def test_details(self): loader = DictLoader({"foo.html": "\n\n{{"}) with self.assertRaises(ParseError) as cm: loader.load("foo.html") self.assertEqual("Missing end expression }} at foo.html:3", str(cm.exception)) self.assertEqual("foo.html", cm.exception.filename) self.assertEqual(3, cm.exception.lineno)
def test_include(self): loader = DictLoader({ "index.html": '{% include "header.html" %}\nbody text', "header.html": "header text", }) self.assertEqual(loader.load("index.html").generate(), b"header text\nbody text")
def test_error_line_number_include(self): loader = DictLoader({"base.html": "{% include 'sub.html' %}", "sub.html": "{{1/0}}"}) try: loader.load("base.html").generate() self.fail("did not get expected exception") except ZeroDivisionError: self.assertTrue("# sub.html:1 (via base.html:1)" in traceback.format_exc())
def test_unextended_block(self): loader = DictLoader(self.templates) name = "<script>" self.assertEqual(loader.load("escaped_block.html").generate(name=name), b"base: <script>") self.assertEqual(loader.load("unescaped_block.html").generate(name=name), b"base: <script>")
def test_non_ascii_name(self): if PY3 and is_coverage_running(): try: os.fsencode(u"t\u00e9st.html") except UnicodeEncodeError: self.skipTest("coverage tries to access unencodable filename") loader = DictLoader({u"t\u00e9st.html": "hello"}) self.assertEqual(loader.load(u"t\u00e9st.html").generate(), b"hello")
def test_error_line_number_extends_base_error(self): loader = DictLoader({"base.html": "{{1/0}}", "sub.html": "{% extends 'base.html' %}"}) try: loader.load("sub.html").generate() self.fail("did not get expected exception") except ZeroDivisionError: exc_stack = traceback.format_exc() self.assertTrue("# base.html:1" in exc_stack)
def test_relative_load(self): loader = DictLoader({ "a/1.html": "{% include '2.html' %}", "a/2.html": "{% include '../b/3.html' %}", "b/3.html": "ok", }) self.assertEqual(loader.load("a/1.html").generate(), b"ok")
def test_error_line_number_expression(self): loader = DictLoader({"test.html": """one two{{1/0}} three """}) try: loader.load("test.html").generate() except ZeroDivisionError: self.assertTrue("# test.html:2" in traceback.format_exc())
def test_multi_includes(self): loader = DictLoader( {"a.html": "{% include 'b.html' %}", "b.html": "{% include 'c.html' %}", "c.html": "{{1/0}}"} ) try: loader.load("a.html").generate() self.fail("did not get expected exception") except ZeroDivisionError: self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in traceback.format_exc())
def test_error_line_number_directive(self): loader = DictLoader({"test.html": """one two{%if 1/0%} three{%end%} """}) try: loader.load("test.html").generate() except ZeroDivisionError: self.assertTrue("# test.html:2" in traceback.format_exc())
def test_default_on(self): loader = DictLoader(self.templates, autoescape="xhtml_escape") name = "Bobby <table>s" self.assertEqual(loader.load("escaped.html").generate(name=name), b"Bobby <table>s") self.assertEqual(loader.load("unescaped.html").generate(name=name), b"Bobby <table>s") self.assertEqual(loader.load("default.html").generate(name=name), b"Bobby <table>s") self.assertEqual( loader.load("include.html").generate(name=name), b"escaped: Bobby <table>s\n" b"unescaped: Bobby <table>s\n" b"default: Bobby <table>s\n", )
def test_error_line_number_module(self): loader = DictLoader({ "base.html": "{% module Template('sub.html') %}", "sub.html": "{{1/0}}", }, namespace={"_modules": ObjectDict({"Template": lambda path, **kwargs: loader.load(path).generate(**kwargs)})}) try: loader.load("base.html").generate() except ZeroDivisionError: exc_stack = traceback.format_exc() self.assertTrue('# base.html:1' in exc_stack) self.assertTrue('# sub.html:1' in exc_stack)
def test_error_line_number_module(self): loader = DictLoader( {"base.html": "{% module Template('sub.html') %}", "sub.html": "{{1/0}}"}, namespace={"_tt_modules": ObjectDict(Template=lambda path, **kwargs: loader.load(path).generate(**kwargs))}, ) try: loader.load("base.html").generate() self.fail("did not get expected exception") except ZeroDivisionError: exc_stack = traceback.format_exc() self.assertTrue("# base.html:1" in exc_stack) self.assertTrue("# sub.html:1" in exc_stack)
def test_whitespace_directive(self): loader = DictLoader({ "foo.html": """\ {% whitespace oneline %} {% for i in range(3) %} {{ i }} {% end %} {% whitespace all %} pre\tformatted """}) self.assertEqual(loader.load("foo.html").generate(), b" 0 1 2 \n pre\tformatted\n")
def test_manual_minimize_whitespace(self): # Whitespace including newlines is allowed within template tags # and directives, and this is one way to avoid long lines while # keeping extra whitespace out of the rendered output. loader = DictLoader({'foo.txt': """\ {% for i in items %}{% if i > 0 %}, {% end %}{# #}{{i }}{% end %}""", }) self.assertEqual(loader.load("foo.txt").generate(items=range(5)), b"0, 1, 2, 3, 4")
def test_extends(self): loader = DictLoader({ "base.html": """\ <title>{% block title %}default title{% end %}</title> <body>{% block body %}default body{% end %}</body> """, "page.html": """\ {% extends "base.html" %} {% block title %}page title{% end %} {% block body %}page body{% end %} """, }) self.assertEqual(loader.load("page.html").generate(), b"<title>page title</title>\n<body>page body</body>\n")
def test_error_line_number_extends_sub_error(self): loader = DictLoader({ "base.html": "{% block 'block' %}{% end %}", "sub.html": """ {% extends 'base.html' %} {% block 'block' %} {{1/0}} {% end %} """}) try: loader.load("sub.html").generate() except ZeroDivisionError: self.assertTrue("# sub.html:4 (via base.html:1)" in traceback.format_exc())
def test_default_off(self): loader = DictLoader(self.templates, autoescape=None) name = "Bobby <table>s" self.assertEqual(loader.load("escaped.html").generate(name=name), b("Bobby <table>s")) self.assertEqual(loader.load("unescaped.html").generate(name=name), b("Bobby <table>s")) self.assertEqual(loader.load("default.html").generate(name=name), b("Bobby <table>s")) self.assertEqual(loader.load("include.html").generate(name=name), b("escaped: Bobby <table>s\n" "unescaped: Bobby <table>s\n" "default: Bobby <table>s\n"))
def test_whitespace_by_loader(self): templates = {"foo.html": "\t\tfoo\n\n", "bar.txt": "\t\tbar\n\n"} loader = DictLoader(templates, whitespace="all") self.assertEqual(loader.load("foo.html").generate(), b"\t\tfoo\n\n") self.assertEqual(loader.load("bar.txt").generate(), b"\t\tbar\n\n") loader = DictLoader(templates, whitespace="single") self.assertEqual(loader.load("foo.html").generate(), b" foo\n") self.assertEqual(loader.load("bar.txt").generate(), b" bar\n") loader = DictLoader(templates, whitespace="oneline") self.assertEqual(loader.load("foo.html").generate(), b" foo ") self.assertEqual(loader.load("bar.txt").generate(), b" bar ")
def test_whitespace_by_filename(self): # Default whitespace handling depends on the template filename. loader = DictLoader({ "foo.html": " \n\t\n asdf\t ", "bar.js": " \n\n\n\t qwer ", "baz.txt": "\t zxcv\n\n", "include.html": " {% include baz.txt %} \n ", "include.txt": "\t\t{% include foo.html %} ", }) # HTML and JS files have whitespace compressed by default. self.assertEqual(loader.load("foo.html").generate(), b"\nasdf ") self.assertEqual(loader.load("bar.js").generate(), b"\nqwer ") # TXT files do not. self.assertEqual(loader.load("baz.txt").generate(), b"\t zxcv\n\n") # Each file maintains its own status even when included in # a file of the other type. self.assertEqual(loader.load("include.html").generate(), b" \t zxcv\n\n\n") self.assertEqual(loader.load("include.txt").generate(), b"\t\t\nasdf ")
def __init__(self, port=8080, url=None, debug=False, **args): # pylint: disable=super-init-not-called; Configurable classes use initialize() instead of # __init__() url = url or 'http://*****:*****@' + urlparts.hostname, render_email_auth_message=self._render_email_auth_message, **args) self._message_templates = DictLoader(meetling.server.templates.MESSAGE_TEMPLATES, autoescape=None)
def test_non_ascii_name(self): loader = DictLoader({u"t\u00e9st.html": "hello"}) self.assertEqual(loader.load(u"t\u00e9st.html").generate(), b"hello")
class MeetlingServer(HTTPServer): """Meetling server. .. attribute:: app Underlying :class:`meetling.Meetling` application. .. attribute:: port See ``--port`` command line option. .. attribute:: url See ``--url`` command line option. .. attribute:: debug See ``--debug`` command line option. Additional *args* are passed to the :class:`meetling.Meetling` constructor and any errors raised by it are passed through. """ def __init__(self, port=8080, url=None, debug=False, **args): # pylint: disable=super-init-not-called; Configurable classes use initialize() instead of # __init__() url = url or 'http://*****:*****@' + urlparts.hostname, render_email_auth_message=self._render_email_auth_message, **args) self._message_templates = DictLoader(meetling.server.templates.MESSAGE_TEMPLATES, autoescape=None) def initialize(self, *args, **kwargs): # Configurable classes call initialize() instead of __init__() self.__init__(*args, **kwargs) def run(self): """Run the server.""" self.app.update() self.listen(self.port) IOLoop.instance().start() def _render_email_auth_message(self, email, auth_request, auth): template = self._message_templates.load('email_auth') msg = template.generate(email=email, auth_request=auth_request, auth=auth, app=self.app, server=self).decode() return '\n\n'.join([filter_whitespace('oneline', p.strip()) for p in re.split(r'\n{2,}', msg)])
v = Column(String) DCLR_BASE.metadata.create_all(SQLITE_ENGINE) MAKE_SESSION = sessionmaker(bind=SQLITE_ENGINE) TEMP_LOADER = DictLoader({ 'rss.xml': '''<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0"> <channel> <title>{{ site }}</title> <link>http://wils519.herokuapp.com/rss/{{ site }}</link> <description>https://github.com/wanghan519/heroku_tornado</description> {% for i in soup %} <item> <title>{{ i[0] }}</title> <link>{{ i[1] }}</link> <pubDate>{{ i[2] }}</pubDate> <description>{{ i[3] }}</description> </item> {% end %} </channel> </rss> ''' }) class MyHandler(RequestHandler): def initialize(self): self.db = MAKE_SESSION()
def test_include(): loader = DictLoader({ "index.html": '{% include "header.html" %}\nbody text', "header.html": "header text", }) print loader.load("index.html").generate()
def __init__( self, app: micro.Application, handlers: Sequence[Handler], port: int = 8080, url: str = None, client_path: str = 'client', client_modules_path: str = '.', client_service_path: str = None, debug: bool = False, *, client_config: ClientConfigArg = {}, client_shell: Sequence[str] = [], client_map_service_key: str = None) -> None: url = url or 'http://*****:*****@noyainrain/micro/service.js'), 'map_service_key': None, 'description': 'Social micro web app', 'color': '#08f', **client_config, # type: ignore 'shell': list(client_config.get('shell') or []) } # type: Server.ClientConfig # Compatibility with client attributes (deprecated since 0.40.0) self.client_path = self.client_config['path'] self.client_modules_path = self.client_config['modules_path'] self.client_service_path = self.client_config['service_path'] self.client_shell = self.client_config['shell'] self.client_map_service_key = self.client_config['map_service_key'] self.app.email = 'bot@' + urlparts.hostname self.app.render_email_auth_message = self._render_email_auth_message def get_activity(*args: str) -> Activity: # pylint: disable=unused-argument; part of API return self.app.activity self.handlers = [ # API (r'/api/login$', _LoginEndpoint), (r'/api/users/([^/]+)$', _UserEndpoint), (r'/api/users/([^/]+)/set-email$', _UserSetEmailEndpoint), (r'/api/users/([^/]+)/finish-set-email$', _UserFinishSetEmailEndpoint), (r'/api/users/([^/]+)/remove-email$', _UserRemoveEmailEndpoint), (r'/api/settings$', _SettingsEndpoint), # Compatibility with non-object Activity (deprecated since 0.14.0) make_activity_endpoint(r'/api/activity/v2', get_activity), *make_list_endpoints(r'/api/activity(?:/v1)?', get_activity), (r'/api/activity/stream', ActivityStreamEndpoint, {'get_activity': cast(object, get_activity)}), # Provide alias because /api/analytics triggers popular ad blocking filters (r'/api/(?:analytics|stats)/statistics/([^/]+)$', _StatisticEndpoint), (r'/api/(?:analytics|stats)/referrals$', _ReferralsEndpoint), (r'/files$', _FilesEndpoint), # type: ignore[misc] (r'/files/([^/]+)$', _FileEndpoint), # type: ignore[misc] *handlers, # UI (r'/log-client-error$', _LogClientErrorEndpoint), (r'/index.html$', _Index), (r'/manifest.webmanifest$', _WebManifest), # type: ignore (r'/manifest.js$', _BuildManifest), # type: ignore (r'/static/{}$'.format(self.client_service_path), _Service), # type: ignore (r'/static/(.*)$', _Static, {'path': self.client_path}), # type: ignore (r'/.*$', UI), # type: ignore ] # type: List[Handler] application = Application( self.handlers, compress_response=True, # type: ignore[arg-type] template_path=self.client_path, debug=self.debug, server=self) # Install static file handler manually to allow pre-processing cast(_ApplicationSettings, application.settings).update({'static_path': self.client_path}) self._server = HTTPServer(application) self._garbage_collect_files_task = None # type: Optional[Task[None]] self._empty_trash_task = None # type: Optional[Task[None]] self._collect_statistics_task = None # type: Optional[Task[None]] self._message_templates = DictLoader(templates.MESSAGE_TEMPLATES, autoescape=None) self._micro_templates = Loader(os.path.join(self.client_path, self.client_modules_path, '@noyainrain/micro'))
class Server: """Server for micro apps. The server may optionally serve the client in :attr:`client_config` *path*. All files from *modules_path*, ``manifest.webmanifest``, ``manifest.js`` and the script at *service_path* are delivered, with a catch-all for ``index.html``. Also, the server may act as a dynamic build system for the client. ``index.html`` is rendered as template. A web app manifest ``manifest.webmanifest`` and a build manifest ``manifest.js`` (see *shell*) are generated. .. attribute:: app Underlying :class:`micro.Application`. .. attribute:: handlers Table of request handlers. It is a list of tuples, mapping a URL regular expression pattern to a :class:`tornado.web.RequestHandler` class. .. attribute:: port See ``--port`` command line option. .. attribute:: url See ``--url`` command line option. .. attributes:: client_path Client location from where static files and templates are delivered. .. deprecated:: 0.40.0 Use :attr:`client_config` instead. .. attribute:: client_service_path Location of client service worker script. Defaults to the included micro service worker. .. deprecated:: 0.40.0 Use :attr:`client_config` instead. .. attribute: client_shell Set of files that make up the client shell. See :func:`micro.util.look_up_files`. .. deprecated:: 0.40.0 Use :attr:`client_config` instead. .. attribute:: client_map_service_key See ``--client-map-service-key`` command line option. .. deprecated:: 0.40.0 Use :attr:`client_config` instead. .. attribute:: debug See ``--debug`` command line option. .. attribute:: client_config Client configuration: - ``path``: Location from where static files and templates are delivered - ``service_path``: Location of service worker script. Defaults to the included micro service worker. - ``shell``: Set of files that make up the client shell. See :func:`micro.util.look_up_files`. - ``map_service_key``: See ``--client-map-service-key`` command line option - ``description``: Short description of the service - ``color``: CSS primary color of the service .. deprecated:: 0.21.0 Constructor options as positional arguments. Use keyword arguments instead. """ ClientConfig = TypedDict('ClientConfig', { 'path': str, 'modules_path': str, 'service_path': str, 'shell': Sequence[str], 'map_service_key': Optional[str], 'description': str, 'color': str }) ClientConfigArg = TypedDict('ClientConfigArg', { 'path': str, 'modules_path': str, 'service_path': str, 'shell': Sequence[str], 'map_service_key': Optional[str], 'description': str, 'color': str }, total=False) def __init__( self, app: micro.Application, handlers: Sequence[Handler], port: int = 8080, url: str = None, client_path: str = 'client', client_modules_path: str = '.', client_service_path: str = None, debug: bool = False, *, client_config: ClientConfigArg = {}, client_shell: Sequence[str] = [], client_map_service_key: str = None) -> None: url = url or 'http://*****:*****@noyainrain/micro/service.js'), 'map_service_key': None, 'description': 'Social micro web app', 'color': '#08f', **client_config, # type: ignore 'shell': list(client_config.get('shell') or []) } # type: Server.ClientConfig # Compatibility with client attributes (deprecated since 0.40.0) self.client_path = self.client_config['path'] self.client_modules_path = self.client_config['modules_path'] self.client_service_path = self.client_config['service_path'] self.client_shell = self.client_config['shell'] self.client_map_service_key = self.client_config['map_service_key'] self.app.email = 'bot@' + urlparts.hostname self.app.render_email_auth_message = self._render_email_auth_message def get_activity(*args: str) -> Activity: # pylint: disable=unused-argument; part of API return self.app.activity self.handlers = [ # API (r'/api/login$', _LoginEndpoint), (r'/api/users/([^/]+)$', _UserEndpoint), (r'/api/users/([^/]+)/set-email$', _UserSetEmailEndpoint), (r'/api/users/([^/]+)/finish-set-email$', _UserFinishSetEmailEndpoint), (r'/api/users/([^/]+)/remove-email$', _UserRemoveEmailEndpoint), (r'/api/settings$', _SettingsEndpoint), # Compatibility with non-object Activity (deprecated since 0.14.0) make_activity_endpoint(r'/api/activity/v2', get_activity), *make_list_endpoints(r'/api/activity(?:/v1)?', get_activity), (r'/api/activity/stream', ActivityStreamEndpoint, {'get_activity': cast(object, get_activity)}), # Provide alias because /api/analytics triggers popular ad blocking filters (r'/api/(?:analytics|stats)/statistics/([^/]+)$', _StatisticEndpoint), (r'/api/(?:analytics|stats)/referrals$', _ReferralsEndpoint), (r'/files$', _FilesEndpoint), # type: ignore[misc] (r'/files/([^/]+)$', _FileEndpoint), # type: ignore[misc] *handlers, # UI (r'/log-client-error$', _LogClientErrorEndpoint), (r'/index.html$', _Index), (r'/manifest.webmanifest$', _WebManifest), # type: ignore (r'/manifest.js$', _BuildManifest), # type: ignore (r'/static/{}$'.format(self.client_service_path), _Service), # type: ignore (r'/static/(.*)$', _Static, {'path': self.client_path}), # type: ignore (r'/.*$', UI), # type: ignore ] # type: List[Handler] application = Application( self.handlers, compress_response=True, # type: ignore[arg-type] template_path=self.client_path, debug=self.debug, server=self) # Install static file handler manually to allow pre-processing cast(_ApplicationSettings, application.settings).update({'static_path': self.client_path}) self._server = HTTPServer(application) self._garbage_collect_files_task = None # type: Optional[Task[None]] self._empty_trash_task = None # type: Optional[Task[None]] self._collect_statistics_task = None # type: Optional[Task[None]] self._message_templates = DictLoader(templates.MESSAGE_TEMPLATES, autoescape=None) self._micro_templates = Loader(os.path.join(self.client_path, self.client_modules_path, '@noyainrain/micro')) def start(self) -> None: """Start the server.""" self.app.update() # type: ignore self._garbage_collect_files_task = self.app.start_garbage_collect_files() self._empty_trash_task = self.app.start_empty_trash() self._collect_statistics_task = self.app.analytics.start_collect_statistics() self._server.listen(self.port) async def stop(self) -> None: """Stop the server.""" self._server.stop() if self._garbage_collect_files_task: await cancel(self._garbage_collect_files_task) if self._empty_trash_task: await cancel(self._empty_trash_task) if self._collect_statistics_task: await cancel(self._collect_statistics_task) def run(self) -> None: """Start the server and run it continuously.""" self.start() loop = get_event_loop() def _on_sigint() -> None: async def _stop() -> None: await self.stop() loop.stop() ensure_future(_stop()) loop.add_signal_handler(SIGINT, _on_sigint) getLogger(__name__).info('Started server at %s/', self.url) loop.run_forever() def rewrite(self, url: str, *, reverse: bool = False) -> str: """Rewrite an internal file *url* to a public URL. If *reverse* is ``True``, mapping is done in the opposite direction. """ if reverse: prefix = f'{self.url}/files/' return f'file:/{url[len(prefix):]}' if url.startswith(prefix) else url return f'{self.url}/files/{url[6:]}' if url.startswith('file:/') else url def _render_email_auth_message(self, email, auth_request, auth): template = self._message_templates.load('email_auth') msg = template.generate(email=email, auth_request=auth_request, auth=auth, app=self.app, server=self).decode() return '\n\n'.join([filter_whitespace('oneline', p.strip()) for p in re.split(r'\n{2,}', msg)])
def test_whitespace_by_loader(self): templates = { "foo.html": "\t\tfoo\n\n", "bar.txt": "\t\tbar\n\n", } loader = DictLoader(templates, whitespace='all') self.assertEqual(loader.load("foo.html").generate(), b"\t\tfoo\n\n") self.assertEqual(loader.load("bar.txt").generate(), b"\t\tbar\n\n") loader = DictLoader(templates, whitespace='single') self.assertEqual(loader.load("foo.html").generate(), b" foo\n") self.assertEqual(loader.load("bar.txt").generate(), b" bar\n") loader = DictLoader(templates, whitespace='oneline') self.assertEqual(loader.load("foo.html").generate(), b" foo ") self.assertEqual(loader.load("bar.txt").generate(), b" bar ")
def test_custom_namespace(self): loader = DictLoader({"test.html": "{{ inc(5) }}"}, namespace={"inc": lambda x: x + 1}) self.assertEqual(loader.load("test.html").generate(), b"6")