Quickstart
This page shows introduction to Glass. The content of this page is short, as Glass is just micro-framework. The code examples are self explanatory and it is easy to understand.
contents:
On This Page …
Introduction
First, install from pypi:
$ pip install glass-web
You can clone it from github with git.
$ git clone https://github.com/horlarwumhe/glass.git
$ cd glass
$ python setup.py install
$ pip install -r requirements.txt
Your first code;
from glass import GlassApp
app = GlassApp()
@app.route('/')
def home():
return 'This is home page'
Starting The Server
You can use any wsgi web server to start the app. gunicorn, cheroot, or boring.
$ pip install boring
$ boring first:app --reload
Assuming the source is saved as first.py.
With gunicorn ;
$ gunicorn first:app --reload
Note
To use builtin server, add app.run to your code.
# file first.py
from glass import GlassApp
app = GlassApp()
@app.route('/')
def home():
return "Hello"
app.run()
$ python first.py
check app.run for usage.
Routing
from glass import GlassApp
app = GlassApp()
@app.route('/')
def home():
return 'This is home page'
from glass import GlassApp
app = GlassApp()
@app.route('/login/')
def login():
return 'This is login page'
@app.get('/')
def do_get():
# for GET method
return 'Hello'
@app.post('/post/')
def do_post():
# for POST method
return "Hello"
Using variable url rule.
@app.route('/<user>/')
def test(user):
return 'Hello %s'%user
@app.route('/reset/<code>')
def reset(code):
# do_something_with(code)
return 'Hello'
The url rule can take optional converter.
- int
<int:param>will match integer only [0-9]
- str
<str:name>will match match all except/and space.
- path
<path:file>will match all except space. Usually for files.
@app.route('/read/<int:post_id>')
def read(post_id):
assert isinstance(post_id,int)
return 'Hello'
@app.route('/view/<str:username>')
def view(username):
return username
@app.route('/view/<path:file>')
def read_file(file):
return FileResponse(file)
# /read/<int:post_id> will match /read/82
# but not /read/fssj
Note
If the url rule doesn’t end with slash e.g.
'/post/<post_id>', this url/post/1/will return404 Not Found, but/post/1will match.If the url rule ends with slash e.g.
/user/login/, user using this/user/loginwill be redirected to the original url/user/login/
By default, each url function allow GET request method. Glass will return 405 Method Not Allowed if any other request method is used. You need to provide other methods to the view.
from glass import request
@app.route('/login/',methods=['GET','POST','PUT'])
def login():
if request.method == 'POST':
do_something()
elif request.method == 'PUT':
do_other_thing()
else:
# GET method
do()
return 'Hello'
@app.route('/delete/',methods=['GET',"DELETE"])
def delete():
if request.method == 'DELETE':
do_delete()
else:
# GET
do_get()
return "Hello"
Response
Each function handling a url rule must return a valid response. The view can return the following valid responses.
Return str;
@app.route('/')
def home():
return 'Hello'
Return response with status code;
@app.route('/')
def home():
return 'Hello',200
Return dict.
If the view return dict, the response headers Content_Type will be set to application/json
and the response dict will be converted to json.
@app.route('/')
def home():
return {'name':'username','id':2},200
# returning code is optional
Response Object
To have more control over the response e.g. setting headers, cookies, content_type, use Response object.
from glass import Response
from glass import GlassApp
app = GlassApp()
@app.route('/')
def home():
# set headers
headers = {'header1':'value1','header2':'value2'}
# or as list
# headers = [('header1', 'value1'), ('header2', 'value2')]
# set content_type
content_type = 'text/plain'
# set response code
code = 200
response = Response('Hello',headers=headers,
content_type=content_type,status_code=code)
return response
Return json
Use
glass.response.JsonResponseto return response object as json.
from glass.response import JsonResponse
@app.route('/api/user/<int:user_id/')
def get_user(user_id):
user = get_user_from_db(user_id)
data = {
'name':user.name,
'email':user.email
}
return JsonResponse(user)
JsonResponse takes same arguments as Response.
Redirect
To redirect users to another url, use redirect().
from glass import redirect
from glass import session
@app.route('/')
def home():
name = session.get('name')
if not name:
return redirect('/login')
return "Hello %s"%name
@app.route('/login',methods=['GET',"POST"])
def login():
name = request.post.get('name','')
session['name'] = name
return 'Hello'
Sending Files
from glass.response import FileResponse
@app.route('/file/<path:filename>')
def send_file(filename):
return FileResponse(filename)
Handling Errors
In case an exception occurs in the application, you can register a function to call when the error occurs.
The function can be registered using error code or exception class.
from glass import GlassApp, request
app = GlassApp()
@app.error(404)
def not_found(error):
r = 'The url %s not found'%request.path
return r, 404
@app.error(500)
def internal_error(error):
return 'Hoooops, Internal Error ', 500
Handling error with exception class.
# you must set DEBUG to False for this work
app.config['DEBUG'] = False
@app.error(NameError)
def handle(exc):
assert isinstance(exc,NameError)
return 'Hoooops, that is NameError....'
from somemodule.exceptions import FooError
@app.error(FooError)
def foo_handler(exc):
return "FooError occurs"
@app.error(Exception)
def err_handler(exc):
return "%s error occurs"%exc.__class__
App API
Use this decorator to register function(s) to call before each app request. This function can be used to open db connection or load logged in user.
from glass import GlassApp, request,session
app = GlassApp()
@app.route('/')
def home():
if request.user is None:
user = 'Guest'
else:
user = request.user.username
return 'Hello %s'%user
@app.before_request
def load_user():
id = session.get('user_id')
if id:
user = get_user_from_db(id)
request.user = user
else:
request.user = None
# make sure set request.user = value
# to avoid getting AttributeError in the
# view function
If the any of the functions of .before_request returns response, the response will be used and the view function of the request url will not be called.
@app.before_request
def unavailable():
return "The site is under development"
Use this decorator to register a fuction(s) to call after each request.
@app.after_request
def after(response):
response.set_header('name','value')
return response
The function takes one argument, Response object and returns the response object.
Session
The session object allows you to store information about a request. The data store in session are different for different requests.
To use session, you need to set app secret_key.
from glass import session,request
from glass import GlassApp
from glass import redirect
app = GlassApp()
#
app.config['SECRET_KEY'] = 'some secret'
@app.route('/')
def home():
name = session.get('name')
if not name:
return 'Hello guest'
return 'Hello %s <a href="/del">remove</a>'%name
@app.route('/set',methods=['GET','POST'])
def set_name():
form = '''
<form method='POST'>
<input type='text' name='username'>
<input type='submit'>
</form>
'''
if request.method == 'POST':
name = request.post.get('username')
if name:
session['name'] = name
return redirect('/')
return form
@app.route('/del')
def remove_name():
session.pop('name')
return redirect('/')
Session class is dict object, so all methods of dict are available.
Note
Like flask, session data are stored in the cookie sent to the browser, unlike django which save session data inside database.
The default method used to encode session data only guarantee the integrity of the cookie. Anyone can decode and see the content of the cookie, but it can`t be modified, because the sha1 hash of cookie is sent with it. If an hacker modified the cookie, it will be imposible to recompute the hash value unless the hacker has access to the app secret key.
You can write your own session storage to manage session.
from glass import session
from glass import current_app
from glass import request
class MySessionManager:
# must define two methods, open() and save()
def open(self):
# session_cookie_name
name = current_app.config["SESSION_COOKIE_NAME"]
cookie = request.cookies.get(name)
if cookie:
# you implement this function
# get the session data from where it is stored
data = get_session_data(cookie)
# data must be dict
# bind the data to the current request
session.bind(data)
else:
session.bind({})
def save(self,response):
# save current session data and get cookie
# you implement this function
cookie = save_data(session.session_data)
name = current_app.config['SESSION_COOKIE_NAME']
# you need to set cookies attributes
# expires, max-age,httponly,secure,samesite
#
response.set_cookie(name,cookie,...)
app = GlassApp()
app.session_cls = MySessionManager()
Note
Session data are threading.local instance. This make the data thread safe on multi-thread web server.
Message Flashing
from glass import GlassApp, flash
@app.route('/')
def home():
return render('index.html')
@app.route('/form',method=['GET','POST']):
def form():
if request.method == 'POST':
name = request.post.get('name')
if len(name) < 10:
flash('name too short')
else:
flash('Hello %s'%name)
return render('form.html')
use get_flash_messages in the template
# form.html
{% for message in get_flash_messages %}
<p> {{message}} </p>
{% endfor %}
<form method='POST'>
<input type='text' name='name'>
<button>go</button>
<form>
Working With Request Data
Request object contains current request data. Such as request headers, method, cookies, HTML form data and files sent to the server.
HTML form;
<form method='POST'>
<input type='text' name='username'>
<input type='password' name='password'>
</form>
from glass import request
@app.route('/home',methods=['GET','POST'])
def home():
if request.method == 'POST':
name = request.post.get('username')
return 'Hello %s'%name
return 'this is home'
Working With Files
To upload files, dont forget to set enctype="multipart/form-data" in your html form.
<form enctype="multipart/form-data" method="POST">
<input type='file' name='userpic'>
<button> go</button>
</form>
from glass import request
@app.route('/home',methods=['GET','POST'])
def home():
if request.method == 'POST':
file = request.files.get('userpic')
# get the filename
# name = file.filename
if file:
file.save_as('/location/on/filesytem')
return 'Hello'
Note
request is made as global object. Despite being global,the object is thread safe. The request relies on WSGI environ which is attached to the request with threading.local.
Read more on Request for other methods.
Configuration
The configuration pattern used is similar to flask. All the config values are stored in dict.
Glass has some predefined configurations. Read more at configuration
app = GlassApp()
class CONFIG:
DEBUG = True
KEY = 'value'
DB_ENGINE = 'postgresql://user:password@localhost:5432/glass'
app.config.from_object(CONFIG)
# config from dict
config = {'DEBUG':True,'KEY':'value'}
app.config.from_dict(config)
# config from json
app.config.from_json('path/to/file.json')
# the configurations can be accesed as
app.config['DEBUG']
app.config['KEY']
current app
The current app object can accessed from anywhere.
from glass import current_app
# this only works with active http request
def connect_db():
db = current_app.config['DB_ENGINE']
do_something(db)
Static Files
Glass will look for static folder in the current working to serve static files (css,js,images,…). You can set another directory to find static files.
app = GlassApp()
app.config['STATIC_FOLDER'] ='/path/to/files'
Like flask and django, default url for static files is /static/.
Template
Glass comes with template engine. The docs here show how to use the template engine with Glass. The full docs is available in the template documentation.
Template Quickstart
The template syntax is very similar to django template.
from glass import render_template as render, import render_string
from glass.response import Response
@app.route('/')
def home():
return render('index.html',context={})
@app.route('/<user>')
def get_user(user):
template = '''
Hello <b> {{user}} </b>
'''
return render_string(template,user=user)
@app.route('/login',methods=['GET',"POST"])
def login():
name = email = ''
if request.method == 'POST':
name = request.post.get('name')
email = request.post.get('email')
res = render('login.html',name=name,email=email)
return Response(res)
<!-- file login.html -->
<body>
{% if name %}
<div> Hello {{name}} </div>
<div> Your email is {{email}} </div>
{%else %}
Hello guest
{% endif %}
<body>
Glass will look for templates folder in the current working directory to load templates. You can set another directory for templates.
app = GlassApp()
app.config['TEMPLATES_FOLDER'] = '/path/to/templates'
set multiple directories;
app.config['TEMPLATES_FOLDER'] = ('/path/1/templates','/path/2/templates')
The template is configured with the following global variables. They can be accessed in the template.
create global variable;
from datetime import datetime as dt
app.template_env.globals['date'] = dt.now
in the template file;
<b> {{date}} </b>
Template Filters
Filters are python function which modify variable in the template.
<b> {{name|upper}} </b>
The filter here is upper which modifies the value of name.
New template filter can be added to the template.
using decorator;
app = GlassApp()
@app.template_env.filter('upper')
def upper(value):
return value.upper()
@app.template_env.filter('secret')
def secret(value):
return value[:5]+'******'
manually register the filter;
def secret(value):
return value[:5]+'******'
app.template_env.filters['secret'] = secret
in the template file;
<div> {{user.username|upper}}</div>
<b> {{user.email|secret}}</b>
Logging
Glass emits messages when incidents that need attention occur in the app. Such as unhandle exceptions. By default, Glass writes logs to process stdout. You can log app messages to another stream using logging module.
# this will log all app messages to file 'app.log'.
import logging
# the logger name used is 'glass.app'
logger = logging.getLogger('glass.app')
handler = logging.FileHandler('app.log')
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s', datefmt='%d/%m/%Y %H:%M:%S %p')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
app = GlassApp()
@app.route('/')
def home():
return "Hello"
@app.error(500)
def error(e):
logger.info('error occurs')
return render('500.html')
check Python logging module documentation.
Using Jinja2
If you want to use jinja2 instead of the builtin template, there is wrapper method for jinja2 API.
# configure your app to use jinja2
app = GlassApp()
app.config['TEMPLATE_BACKEND'] = 'jinja'
app.jinja_env returns JinjaEnvironment which is subclass of jinja2.Environment.
Create a filter for jinja2
@app.jinja_env.register_filter('split')
def join(value,param=''):
return value.split(param)
{{name | split(param=',')}}
Global Value
app.jinja_env.globals['name'] = 'global_value'
Working with database
Glass does not come with ORM. You can use sqlalchemy,ponyorm,sqlobject or use pure SQL using sqlite3 .
Full example
from glass import GlassApp, request,session
from glass.response import Redirect as redirect
from glass.templating import render_template
from glass import flash
app = GlassApp()
app.config['SECRET_KEY'] = 'some secrete'
@app.route('/')
def home():
return render_template('index.html')
@app.route('/logout')
def logout():
session.pop('user_id')
return redirect('/')
@app.route('/login',methods=['GET','POST'])
def login():
error = ''
if request.method == 'POST':
name = request.post.get('username')
if name:
user = db.get(name)
if user:
login_user(user)
return redirect('/')
else:
error = 'invalid username'
else:
error = 'provide username to login'
if error:
flash(error)
return render_template('login.html')
def login_user(user):
session['user_id'] = user.id
@app.before_request
def load_user():
# to avoid getting attribute error
# make sure to set request.user = value
user_id = session.get('user_id')
if user_id:
# get user from db
# you have to implement your own db
# either using sqlalchemy or other orm
user = db.get(id=user_id)
if user:
request.user = user
else:
request.user = None
else:
request.user = None
def login_require(func):
def inner(*args,**kwargs):
if request.user is None:
return redirect('/login')
return func(*args,**kwargs)
return inner
@app.route('/secret')
@login_require
def view():
return 'Hello, you are authenticated'