I am learning web application development, and am trying out the separating of modules just to keep my program more obviously object oriented and easy to navigate / understand.
My first import in Main.py is working fine:
import jinja2
import main_page # <<<- This import of my module works
import os
import webapp2
from string import letters
# loads templates to make our life easier
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir),
autoescape = True)
######## Main App Function ########
app = webapp2.WSGIApplication([('/', MainPage)], debug=True)
My second import in main_page.py isn't working:
import main_handler # <<< -- This import is not working
######## Implementation ########
# main page handler
class MainPage(BaseHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
visits = 0
visit_cookie_str = self.request.cookies.get('visits')
if visit_cookie_str:
cookie_val = check_secure_val(visit_cookie_str)
if cookie_val:
visits = int(cookie_val)
visits += 1
new_cookie_val = make_secure_val(str(visits))
self.response.headers.add_header('Set-Cookie', 'visits=%s' % new_cookie_val)
self.write("You've been here %s times and this is the cookie: \n %s" % (visits, new_cookie_val))
I get this error in the terminal:
File "/Users/James/Developer/Google-App-Engine/Cookies/main_page.py",
line 6, in
class MainPage(BaseHandler): NameError: name 'BaseHandler' is not defined
I've tried changing file names and class names inc ase they were getting confused with other modules. The files are all in the same folder.
This is the **main_handler.py**:
import webapp2
import string
import jinja2
import hashlib
import hmac
SECRET = "test secret"
# global hash functions
def hash_str(s):
return hmac.new(SECRET, s).hexdigest()
def make_secure_val(s):
return "%s|%s" % (s, hash_str(s))
def check_secure_val(h):
val = h.split('|')[0]
if h == make_secure_val(val):
return val
# this is a template with convenience methods
class BaseHandler(webapp2.RequestHandler):
def write(self, *a, **kw):
self.response.out.write(*a, **kw)
def render(self, template, **kw):
self.write(self.render_str(template, **kw))
def render_str(self, template, **params):
t = jinja_env.get_template(template)
return t.render(params)
When you import "main_handler", you can use it like this:
main_handler.make_secure_val
Not like this:
make_secure_val
Related
In my Flask app, I'm using Dependency Injection and here's what my app looks like. I have a service which uses S3 as a datastore and I'm trying to instantiate my app with the service injected (which is injected with the S3 client). However, it doesn't look like the S3 client is correctly instantiated or I'm doing something wildly different.
containers.py
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(modules=[".routes", ".scheduler"])
config = providers.Configuration()
s3_config = dict()
s3_config["path"], s3_config["filters"] = config.get("s3_bucket"), [("member_status_nm", "=", "ACTIVE")]
s3_repository = providers.Singleton(S3Repository, s3_config)
my_service = providers.Factory(
MyService, config, S3Repository
)
Here's my S3Repository:
import logging
import sys
import time
import some_library as lib
class S3Repository:
def __init__(self, s3_config):
self.path, self.columns, self.filters = \
s3_config.get("path", ""), s3_config.get("columns", []), s3_config.get("filters", [])
def fetch(self):
# execute fetch
result = lib.some_fetch_method(self.path, self.columns, self.filters)
return result
and MyService:
import #all relevant imports here
class MyService:
def __init__(self, config: dict, s3_repository: S3Repository) -> None:
logging.info("HealthSignalService(): initializing")
self.config = config["app"]["health_signal_service"]
# prepare s3_repository for the service
self.s3_repository = s3_repository
self.s3_repository.columns, self.s3_repository.filters, self.s3_repository.path = \
["x","y"], ["x1","y1"], "file_path"
def fetch_data(self) -> None:
try:
summary_result = self.s3_repository.fetch()
except (FileNotFoundError, IOError) as e:
print("failure")
return summary_result
def get_data(memberId):
sth = self.fetch_data()
return sth.get(memberId)
and finally tying it together in my routes.py:
#inject
#auth.login_required
def get_signals(
my_service: MyService = Provide[
Container.my_service
],
):
content = request.json
member_id = content["memberId"]
result = my_service.get_signals(member_id)
return jsonify(result)
When I hit my API endpoint I see this error:
summary_result = self.s3_repository.fetch()
TypeError: fetch() missing 1 required positional argument: 'self'
How do I correctly initialize my S3 client while using dependency injection?
CherryPy keeps returning blank pages or with the values I return in the controllers. I rewrote a django and jinja2 version that did work, apparently this one doesn't which is almost identical to the previous mentioned.
I did some pprint's in the tool bit which does fill the request.body with parsed html but doesn't output it when pass is set in the controller. If I return a {'user':True} in the controller that is shown in the form of a simple "User".
with a few examples online and the code of SickBeard I came to the following:
controller:
class RootController(object):
#cherrypy.expose
#cherrypy.tools.render(template="page/home.html")
def index(self):
pass
tool:
class CheetahTool(cherrypy.Tool):
def __init__(self):
cherrypy.Tool.__init__(self, 'on_start_resource',
self._render,
priority=30)
def _render(self, template=None, debug=False):
if cherrypy.response.status > 399:
return
# retrieve the data returned by the handler
data = cherrypy.response.body or {}
template = cherrypy.engine.publish("lookup-template", template).pop()
if template and isinstance(data, dict):
for k,v in data:
template.__setattr__(k, v)
# dump the template using the dictionary
if debug:
try:
cherrypy.response.body = unicode(template).encode('utf-8', 'xmlcharrefreplace')
except Exception as e:
from pprint import pprint
pprint(e.message)
else:
cherrypy.response.body = template.respond()
plugin:
class PageTemplate(Template):
"""
Thank you SickBeard
"""
def __init__(self, base_dir, template, *args, **KWs):
KWs['file'] = os.path.join(base_dir, template)
super(PageTemplate, self).__init__(*args, **KWs)
application = cherrypy.tree.apps['']
config = application.config
self.sbRoot = base_dir
self.sbHttpPort = config['global']['server.socket_port']
self.sbHttpsPort = self.sbHttpPort
self.sbHttpsEnabled = False
if cherrypy.request.headers['Host'][0] == '[':
self.sbHost = re.match("^\[.*\]", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
else:
self.sbHost = re.match("^[^:]+", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
if "X-Forwarded-Host" in cherrypy.request.headers:
self.sbHost = cherrypy.request.headers['X-Forwarded-Host']
if "X-Forwarded-Port" in cherrypy.request.headers:
self.sbHttpPort = cherrypy.request.headers['X-Forwarded-Port']
self.sbHttpsPort = self.sbHttpPort
if "X-Forwarded-Proto" in cherrypy.request.headers:
self.sbHttpsEnabled = True if cherrypy.request.headers['X-Forwarded-Proto'] == 'https' else False
self.sbPID = str(aquapi.PID)
self.menu = [
{ 'title': 'Home', 'key': 'home' },
{ 'title': 'Users', 'key': 'users' },
{ 'title': 'Config', 'key': 'config' },
]
def render(self):
return unicode(self).encode('utf-8', 'xmlcharrefreplace')
class CheetahTemplatePlugin(plugins.SimplePlugin):
def __init__(self, bus, base_dir=None, base_cache_dir=None,
collection_size=50, encoding='utf-8'):
plugins.SimplePlugin.__init__(self, bus)
self.base_dir = base_dir
self.base_cache_dir = base_cache_dir or tempfile.gettempdir()
self.encoding = encoding
self.collection_size = collection_size
def start(self):
self.bus.log('Setting up Cheetah resources')
self.bus.subscribe("lookup-template", self.get_template)
def stop(self):
self.bus.log('Freeing up Cheetah resources')
self.bus.unsubscribe("lookup-template", self.get_template)
self.lookup = None
def get_template(self, name):
"""
Returns Cheetah's template by name.
"""
return PageTemplate(self.base_dir, name)
init:
# Template engine tool
from aquapi.web.tools.template import CheetahTool
cherrypy.tools.render = CheetahTool()
# Tool to load the logged in user or redirect
# the client to the login page
from aquapi.web.tools.user import UserTool
cherrypy.tools.user = UserTool()
from aquapi.web.controllers import RootController
webapp = RootController()
# Let's mount the application so that CherryPy can serve it
app = cherrypy.tree.mount(webapp, '/', os.path.join(self.base_dir, "app.cfg"))
# Template engine plugin
from aquapi.web.plugin.template import CheetahTemplatePlugin
engine.cheetah = CheetahTemplatePlugin(engine,
os.path.join(self.base_dir, 'aquapi/web/templates'),
os.path.join(self.base_dir, 'cache'))
engine.cheetah.subscribe()
In general, to me it's some sort of over-engineering happened in your snippets. CherryPy plugins are usually used for a system task (e.g. put PID-file on engine start, remove it on stop) or for an asynchronous task (e.g. sending email in separate thread). Template rendering happens clearly synchronously to the request handling, so I don't see the point of extracting this logic out of CherryPy tool. There's a class in CherryPy, cherrypy._cptools.HandlerWrapperTool, which demonstrate the suggested approach to wrapping handler return values.
I haven't ever used Cheetah, so my example is Jinja2-based. You will just have to change the templating engine instance (to Cheetah) and correct its calls. The rest is the same.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import types
import cherrypy
import jinja2
path = os.path.abspath(os.path.dirname(__file__))
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 4
}
}
class TemplateTool(cherrypy.Tool):
_engine = None
'''Jinja environment instance'''
def __init__(self):
viewLoader = jinja2.FileSystemLoader(os.path.join(path, 'view'))
self._engine = jinja2.Environment(loader = viewLoader)
cherrypy.Tool.__init__(self, 'before_handler', self.render)
def __call__(self, *args, **kwargs):
if args and isinstance(args[0], (types.FunctionType, types.MethodType)):
# #template
args[0].exposed = True
return cherrypy.Tool.__call__(self, **kwargs)(args[0])
else:
# #template()
def wrap(f):
f.exposed = True
return cherrypy.Tool.__call__(self, *args, **kwargs)(f)
return wrap
def render(self, name = None):
cherrypy.request.config['template'] = name
handler = cherrypy.serving.request.handler
def wrap(*args, **kwargs):
return self._render(handler, *args, **kwargs)
cherrypy.serving.request.handler = wrap
def _render(self, handler, *args, **kwargs):
template = cherrypy.request.config['template']
if not template:
parts = []
if hasattr(handler.callable, '__self__'):
parts.append(handler.callable.__self__.__class__.__name__.lower())
if hasattr(handler.callable, '__name__'):
parts.append(handler.callable.__name__.lower())
template = u'/'.join(parts)
data = handler(*args, **kwargs) or {}
renderer = self._engine.get_template(u'{0}.html'.format(template))
return renderer.render(**data)
cherrypy.tools.template = TemplateTool()
class App:
#cherrypy.expose
def index(self):
'''No renderer applied, CherryPy outputs dict keys'''
return {'user': 123}
#cherrypy.tools.template
def auto(self):
return {'user': 123}
#cherrypy.tools.template(name = 'app/auto')
def manual(self):
return {'user': 234}
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
Along the python file, create directory view/app and put the following in file named auto.html there.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' content='text/html; charset=utf-8' />
<title>Test</title>
</head>
<body>
<p>User: <em>{{ user }}</em></p>
</body>
</html>
Some notes on the TemplateTool. First, you can use it as a decorator in two ways: not making a call, and making a call with template name argument. You can use the tool as any other CherryPy tool in the configuration (e.g. make all controller methods to render templates). Second, following convention-over-configuration principle, the tool when not provided with template name will use classname/methodname.html. Third, the decorator exposes the method, so you don't need to add #cherrypy.expose on top.
I'm writing some tests and I need to use templates in my view without using a file (e.g. via ViewPageTemplateFile). The template must evaluate the tal expressions and access the functions of the view. Here is my current problem. I get an error on evaluating the view in the PageTemplate. I'm not sure whether my code follows best practices.
from plone.app.testing import TEST_USER_NAME, TEST_USER_PASSWORD
from plone.testing.z2 import Browser
from zope.component import getGlobalSiteManager
from zope.publisher.interfaces.browser import IBrowserView
from Products.Five.browser import BrowserView
from zope.pagetemplate.pagetemplate import PageTemplate
class MyView(BrowserView):
def __init__(self, context, request):
self.context = context
self.request = request
def render(self):
template = PageTemplate()
text = '''\
<div tal:on-error="string:view fails"><span tal:replace="view/var"/></div>
<div tal:on-error="string:python fails"><span tal:replace="python:'python works'"/></div>
'''
template.write(text)
return template()
def __call__(self):
return self.render()
def var(self):
return 'my_value'
global_site_manager = getGlobalSiteManager()
global_site_manager.registerAdapter(MyView, (None, None), IBrowserView, 'my_view')
browser=Browser(app.Plone)
browser.addHeader('Authorization',
'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD))
browser.open(app.Plone.absolute_url() + '/##my_view')
print browser.contents
produces following output:
<div>view fails</div>
<div>
python works
</div>
My workaround until now is to create my own class MyPageTemplate(PageTemplate), passing the view's instance as argument in the constructor, and override pt_getContext to populate the namespace with view, context, and request from the instance.
I would like to know whether there is a better solution (best practices).
Here my workaround:
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import TEST_USER_PASSWORD
from plone.testing.z2 import Browser
from zope.component import getGlobalSiteManager
from zope.publisher.interfaces.browser import IBrowserView
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.pagetemplate.pagetemplate import PageTemplate
from zope.interface import Interface
from zope.publisher.browser import IDefaultBrowserLayer
class MyPageTemplate(PageTemplate):
instance = None
def __init__(self, instance):
self.instance = instance
def pt_getContext(self, instance, request, **kw):
namespace = super(MyPageTemplate, self).pt_getContext(**kw)
namespace['view'] = self.instance
namespace['context'] = self.instance.context
namespace['request'] = self.instance.request
return namespace
class MyView(BrowserView):
def __init__(self, context, request):
self.context = context
self.request = request
def render(self):
template = MyPageTemplate(self)
text = '''\
<div tal:on-error="string:request fails">request<span tal:replace="structure request"/> works</div>
<div tal:on-error="string:context fails">context/id=<span tal:replace="context/id"/> works</div>
<div tal:on-error="string:view fails">view/my_var=<span tal:replace="view/my_var"/> works</div>
<div tal:on-error="string:python fails"><span tal:replace="python:'python works'"/></div>
'''
template.write(text)
return template()
def __call__(self):
return self.render()
def my_var(self):
return 'my_value'
global_site_manager = getGlobalSiteManager()
global_site_manager.registerAdapter(MyView, (None, None), IBrowserView, 'my_view')
browser=Browser(app.Plone)
browser.addHeader('Authorization', 'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD))
browser.open(app.Plone.absolute_url() + '/##my_view')
print browser.contents
Currently trying to create a basic blog using Google App Engine in Python. Here's the python code that I am using:
import os
import re
import webapp2
import jinja2
from string import letters
from google.appengine.ext import db
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir), autoescape=True)
class Handler(webapp2.RequestHandler):
def write(self, *a, **kw):
self.response.out.write(*a, **kw)
def render_str(self, template, **params):
t = jinja_env.get_template(template)
return t.render(params)
def render(self, template, **kw):
self.write(self.render_str(template, **kw))
def post_key(name = "dad"):
return db.Key.from_path('blog', name)
class Blogger(db.Model):
name = db.StringProperty()
content = db.TextProperty()
created = db.DateTimeProperty(auto_now_add = True)
def render(self):
self._render_text = self.content.replace('\n', '<br>')
return render_str("post.html", p = self)
class MainPage(Handler):
def get(self):
self.response.write("Visit our blog")
class BlogHandler(Handler):
def get(self):
posts = db.GqlQuery("SELECT * FROM Blogger order by created desc")
self.render("frontblog.html", posts = posts)
class SubmitHandler(Handler):
def get(self):
self.render("temp.html")
def post(self):
name = self.request.get("name")
content = self.request.get("content")
if name and content:
a = Blogger(parent = post_key(), name = name, content = content)
a.put()
self.redirect('/blog/%s' % str(a.key().id()))
else:
error = "Fill in both the columns!"
self.render("temp.html", name = name, content = content, error = error)
class DisplayPost(Handler):
def get(self, post_id):
po = Blogger.get_by_id(post_id)
if po:
self.render("perma.html", po = po)
else:
self.response.write("404 Error")
app = webapp2.WSGIApplication([('/', MainPage),
('/blog', BlogHandler),
('/blog/submit', SubmitHandler)
('/blog/([0-9]+)', DisplayPost)], debug=True)
However, when I try to run this code on my local server, this is what I get as an error:
File "F:\Python 2.7\engineapp1\HelloApp\appapp\main.py", line 66, in <module>
('/blog/([0-9]+)', DisplayPost)], debug=True)
TypeError: 'tuple' object is not callable
What seems to be the problem here?
You forgot to add a comma.
('/blog/submit', SubmitHandler) <---- missed comma over here
('/blog/([0-9]+)', DisplayPost)], debug=True)
It's acting like a function in this case, you're passing a parameter to a tuple which result in an error that the tuple is not callable.
('/blog/submit', SubmitHandler)(parameter)
There's a missing comma at the line:
('/blog/submit', SubmitHandler)
It should be:
('/blog/submit', SubmitHandler),
Without the comma, you have ('/blog/submit', SubmitHandler)('/blog/([0-9]+)', DisplayPost), which is trying to call ('/blog/submit', SubmitHandler) as a function, with '/blog/([0-9]+)' and DisplayPost as parameters. Since that's not a function, but a tuple, you get that error.
everyone : )
I have A database that can search like "http://key.xxxxxx.org:10093/?q="
when type the keyword in url (behind the =mark), it can show lots of json data.
Now i write a simple webapp on GAE use webapp2 : on the /search page, when i type the keyword,
it can parse the jsondata content on the /result page. Here is my part of code :
# -*- coding: utf-8 -*-
import os
import re
import httplib
import json
import urllib
from string import letters
import webapp2
import jinja2
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir),
autoescape = True)
def render_str(template, **params):
t = jinja_env.get_template(template)
return t.render(params)
def renderJson(query):
conn = httplib.HTTPConnection('xxx.xxxxx.org:10093')
conn.request('GET', '/?q=%s' % urllib.quote_plus(query))
res = conn.getresponse()
data = res.read()
j = json.loads(data)
results = []
result = j['data']['group'][0]['resultitem']
for l in result:
for k,v in l.items():
if k == 'word':
results.append(l[k])
return results
class BaseHandler(webapp2.RequestHandler):
def render(self, template, **kw):
self.response.out.write(render_str(template, **kw))
def write(self, *a, **kw):
self.response.out.write(*a, **kw)
class Search(BaseHandler):
def get(self):
self.render("search.html")
def post(self):
q = self.request.get('query')
if q:
self.redirect('/result/?q='+query)
class Result(BaseHandler):
def get(self):
q = self.request.get('query')
self.render('result.html', query = q, result = renderJson(q))
app = webapp2.WSGIApplication([
('/search', Search),
('/result', Result)
],
debug=True)
in the search.html, i wrote
<input type="text" name="q" value="{{query}}"></input>
and in result.html
<p>{{result}}</p>
For getting the parameter from the url you need to use:
q = self.request.get('query')
See the Getting Started guide and the webapp improved documentation.
Then you would pass that parameter to renderJson as renderJson(q), also in renderJson
the second line will need to quote the query string first (and pass the q argument)
conn.request('GET', '/?q=%s' % urllib.quote(query))
Finally for rendering the final output please read the Getting Started Guide.