Fixures¶
Um fixture é definido como “uma peça de equipamento ou de mobiliário, que é fixa em posição num edifício ou veículo”. No nosso caso, um dispositivo elétrico é algo ligado à ação que processa um pedido HTTP, a fim de produzir uma resposta.
Ao processar qualquer solicitações HTTP existem algumas operações opcionais que pode desejar executar. Por exemplo parse o cookie para procurar informações sessão, confirmar uma transação de banco de dados, determinar o idioma preferido do cabeçalho HTTP e lookup internacionalização adequada, etc. Essas operações são opcionais. Algumas ações precisam deles e algumas ações não. Eles também podem depender um do outro. Por exemplo, se as sessões são armazenadas no banco de dados e nossa ação precisa dele, talvez seja necessário analisar o cookie de sessão a partir do cabeçalho, pegar uma conexão do pool de conexão do banco de dados, e - após a ação foi executada - salvar a sessão de volta no banco de dados se os dados foram alterados.
Fixtures PY4WEB fornecem um mecanismo para especificar o que uma ação necessidades para que py4web pode realizar as tarefas necessárias (e ignorar os não necessários) da maneira mais eficiente. Fixures tornar o código eficiente e reduzir a necessidade de código clichê.
Fixtures PY4WEB são semelhantes aos middleware WSGI e BottlePy plug-in, exceto que eles se aplicam a ações individuais, não para todos eles, e pode dependem uns dos outros.
PY4WEB vem com alguns jogos pré-definidos para as ações que precisam sessões, conexões de banco de dados, internacionalização, autenticação e templates. A sua utilização será explicado neste capítulo. O desenvolvedor também é livre para adicionar Fixtures, por exemplo, para lidar com uma terceira língua template do partido ou a lógica sessão de terceiros.
Importante sobre Fixures¶
As we’ve seen in the previous chapter, fixtures are the arguments of the decorator
@action.uses(...)
. You can specify
multiple fixtures in one decorator or you can have multiple decorators.
Also, fixtures can be applied in groups. For example:
preferred = action.uses(session, auth, T, flash)
Então você pode aplicar todo o ao mesmo tempo com:
@action('index.html')
@preferred
def index():
return dict()
Templates Py4web¶
PY4WEB by default uses the yatl template language and provides a fixture for it.
from py4web import action
from py4web.core import Template
@action('index')
@action.uses(Template('index.html', delimiters='[[ ]]'))
def index():
return dict(message="Hello world")
Nota: Este exemplo assume que você criou o aplicativo da App andaimes, para que o index.html template já é criado para você.
O objeto template é um dispositivo elétrico. Ele transforma o `` dict () “ retornado pela ação em uma string usando o arquivo template index.html`. Em um capítulo posterior iremos fornecer um exemplo de como definir um fixture personalizado para usar uma linguagem de template diferente, por exemplo Jinja2.
Tenha em conta que uma vez que o uso de templates é muito comum e uma vez que, muito provavelmente, cada ação usa um template diferente, nós fornecemos um pouco de açúcar sintático, e as duas linhas a seguir são equivalentes:
@action.uses('index.html')
@action.uses(Template('index.html', delimiters='[[ ]]')
Observe que arquivos de template py4web são armazenados em cache na memória RAM. O objecto py4web cache é descrito mais tarde.
The Session fixture¶
Simply speaking, a session can be defined as a way to preserve information that is desired to persist throughout the user’s interaction with the web site or web application. In other words, sessions render the stateless HTTP connection a stateful one. It can be implemented server-side or client-side (by using cookies).
In py4web, the session object is also a Fixture. Here is a typical example of usage to implement a counter.
from py4web import Session, action
session = Session(secret='my secret key')
@action('index')
@action.uses(session)
def index():
counter = session.get('counter', -1)
counter += 1
session['counter'] = counter
return "counter = %i" % counter
Observe que o objeto da sessão tem a mesma interface como um dicionário Python.
By default the session object is stored in a cookie called, signed and encrypted, using the provided secret. If the secret changes existing sessions are invalidated. If the user switches from HTTP to HTTPS or vice versa, the user session is invalidated. Session in cookies have a small size limit (4 kbytes after being serialized and encrypted) so do not put too much into them.
Em py4web sessões são dicionários, mas eles são armazenados usando JSON (JWT especificamente), portanto, você só deve armazenar objetos que são JSON serializado. Se o objeto não é JSON serializado, ele vai ser serializado usando o `` operador __str__`` e algumas informações podem ser perdidas.
Por padrão sessões py4web nunca expiram (a menos que contenham informações de login, mas isso é outra história), mesmo se uma expiração pode ser definido. Outros parâmetros podem ser especificados, bem como:
session = Session(secret='my secret key',
expiration=3600,
algorithm='HS256',
storage=None,
same_site='Lax')
Aqui `` algorithm`` é o algoritmo a ser usado para a assinatura de token JWT.
`` Storage`` é um parâmetro que permite especificar um método alternativo de armazenamento de sessão (por exemplo, redis, ou base de dados).
`` Same_site`` é uma opção que impede CSRF ataques e é ativado por padrão. Você pode ler mais sobre ele aqui <https://www.owasp.org/index.php/SameSite> __.
Sessão em memcache¶
import memcache, time
conn = memcache.Client(['127.0.0.1:11211'], debug=0)
session = Session(storage=conn)
Observe que um segredo não é necessária quando o armazenamento de cookies no memcache porque neste caso o cookie contém apenas o UUID da sessão.
Sessão em Redis¶
import redis
conn = redis.Redis(host='localhost', port=6379)
conn.set = lambda k, v, e, cs=conn.set, ct=conn.ttl: (cs(k, v), e and ct(e))
session = Session(storage=conn)
Aviso: um objecto de armazenamento deve ter `` `` GET`` e métodos set`` e do método set` deve permitir especificar uma expiração. O objecto de ligação redis tem um método ttl` para especificar a expiração, remendo, portanto, que o macaco` método set` ter a assinatura esperada e funcionalidade.
Sessão no banco de dados¶
from py4web import Session, DAL
from py4web.utils.dbstore import DBStore
db = DAL('sqlite:memory')
session = Session(storage=DBStore(db))
Caution: Keep in mind that 'sqlite:memory'
cannot be used in multiprocess environment, the quirk is that your application will still work but in non-deterministic and unsafe mode, since each process/worker will have its own independent in-memory database.
Um segredo não é necessária quando o armazenamento de cookies no banco de dados porque, neste caso, o cookie contém apenas o UUID da sessão.
Also this is one case when a fixture (session) requires another fixture (db). This is handled automatically by py4web and the following are equivalent:
@action.uses(session)
@action.uses(db, session)
Sessão em qualquer lugar¶
Você pode facilmente armazenar sessões em qualquer lugar que você quer. Tudo que você precisa fazer é fornecer ao objeto `` Session`` um objeto `` storage`` com ambos os `` GET`` e `` métodos set``. Por exemplo, imagine que você deseja armazenar sessões no seu sistema de arquivos local:
import os
import json
class FSStorage:
def __init__(self, folder):
self.folder = folder
def get(self, key):
filename = os.path.join(self.folder, key)
if os.path.exists(filename):
with open(filename) as fp:
return json.load(fp)
return None
def set(self, key, value, expiration=None):
filename = os.path.join(self.folder, key)
with open(filename, 'w') as fp:
json.dump(value, fp)
session = Session(storage=FSStorage('/tmp/sessions'))
Deixamos-lhe como um exercício para implementar validade, limitar o número de arquivos por pasta usando subpastas, e implementar o bloqueio de arquivos. No entanto, nós não recomment armazenar sessões no sistema de arquivos: é ineficiente e não escala bem.
The Translator fixture¶
Aqui está um exemplo de uso:
from py4web import action, Translator
import os
T_FOLDER = os.path.join(os.path.dirname(__file__), 'translations')
T = Translator(T_FOLDER)
@action('index')
@action.uses(T)
def index(): return str(T('Hello world'))
A string ‘Olá mundo ` será traduzido com base no arquivo de internacionalização em ‘traduções’ especificados pasta que melhor corresponde ao HTTP `` aceitar-language`` cabeçalho.
Aqui `` Translator`` é uma classe py4web que se estende `` pluralize.Translator`` e também implementa a interface de `` Fixture``.
Podemos facilmente combinar vários Fixtures. Aqui, como exemplo, podemos tornar a acção com um contador que conta “visitas”.
from py4web import action, Session, Translator, DAL
from py4web.utils.dbstore import DBStore
import os
db = DAL('sqlite:memory')
session = Session(storage=DBStore(db))
T_FOLDER = os.path.join(os.path.dirname(__file__), 'translations')
T = Translator(T_FOLDER)
@action('index')
@action.uses(session, T)
def index():
counter = session.get('counter', -1)
counter += 1
session['counter'] = counter
return str(T("You have been here {n} times").format(n=counter))
Agora crie o seguinte arquivo de tradução `` traduções / en.json``:
{"You have been here {n} times":
{
"0": "This your first time here",
"1": "You have been here once before",
"2": "You have been here twice before",
"3": "You have been here {n} times",
"6": "You have been here more than 5 times"
}
}
Ao visitar este site com o navegador preferência de idioma definida para Inglês e recarregar várias vezes você receberá as seguintes mensagens:
This your first time here
You have been here once before
You have been here twice before
You have been here 3 times
You have been here 4 times
You have been here 5 times
You have been here more than 5 times
Agora tente criar um arquivo chamado `` traduções / it.json`` que contém:
{"You have been here {n} times":
{
"0": "Non ti ho mai visto prima",
"1": "Ti ho gia' visto",
"2": "Ti ho gia' visto 2 volte",
"3": "Ti ho visto {n} volte",
"6": "Ti ho visto piu' di 5 volte"
}
}
e definir preferência seu navegador para Italiano.
O fixture flash¶
It is common to want to display “alerts” to the users. Here we refer to them as flash messages. There is a little more to it than just displaying a message to the view because flash messages can have state that must be preserved after redirection. Also they can be generated both server side and client side, there can be only one at the time, they may have a type, and they should be dismissible.
O auxiliar o Flash lida com o lado do servidor deles. Aqui está um exemplo:
from py4web import Flash
flash = Flash()
@action('index')
@action.uses(flash)
def index():
flash.set("Hello World", _class="info", sanitize=True)
return dict()
e no template:
...
<div id="py4web-flash"></div>
...
<script src="js/utils.js"></script>
[[if globals().get('flash'):]]<script>utils.flash([[=XML(flash)]]);</script>[[pass]]
By setting the value of the message in the flash helper, a flash
variable is returned by the action and this trigger the JS in the
template to inject the message in the py4web-flash
DIV which you
can position at your convenience. Also the optional class is applied to
the injected HTML.
Se uma página é redirecionada depois de um flash está definido, o flash é lembrado. Isto é conseguido por pedir o navegador para manter a mensagem temporariamente em um cookie de uma só vez. Depois de redirecionamento a mensagem é enviada de volta pelo navegador para o servidor e os conjuntos de servidor ele novamente automaticamente antes de retornar o conteúdo, a menos que seja substituído por um outro conjunto.
O cliente também pode definir / adicionar mensagens flash chamando:
utils.flash({'message': 'hello world', 'class': 'info'});
py4web defaults to an alert class called info
and most CSS
frameworks define classes for alerts called success
, error
,
warning
, default
, and info
. Yet, there is nothing in py4web
that hardcodes those names. You can use your own class names.
O fixture DAL¶
Nós já usou o `` dispositivo elétrico DAL`` no contexto das sessões, mas talvez você queira ter acesso direto ao objeto DAL com a finalidade de acessar o banco de dados, e não apenas sessões.
PY4WEB, by default, uses the PyDAL (Python Database Abstraction Layer)
which is documented in the next chapter. Here is an example, please
remember to create the databases
folder under your project in case
it doesn’t exist:
from datetime import datetime
from py4web import action, request, DAL, Field
import os
DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
db = DAL('sqlite://storage.db', folder=DB_FOLDER, pool_size=1)
db.define_table('visit_log', Field('client_ip'), Field('timestamp', 'datetime'))
db.commit()
@action('index')
@action.uses(db)
def index():
client_ip = request.environ.get('REMOTE_ADDR')
db.visit_log.insert(client_ip=client_ip, timestamp=datetime.utcnow())
return "Your visit was stored in database"
Notice that the database fixture defines (creates/re-creates) tables
automatically when py4web starts (and every time it reloads this app)
and picks a connection from the connection pool at every HTTP request.
Also each call to the index()
action is wrapped into a transaction
and it commits on_success
and rolls back on_error
.
Caveats about fixtures¶
Desde fixtures são compartilhados por várias ações que você não tem permissão para alterar seu estado, porque não seria seguro para threads. Há uma exceção a esta regra. As ações podem alterar alguns atributos de campos de banco de dados:
from py4web import action, request, DAL, Field
from py4web.utils.form import Form
import os
DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
db = DAL('sqlite://storage.db', folder=DB_FOLDER, pool_size=1)
db.define_table('thing', Field('name', writable=False))
@action('index')
@action.uses(db, 'generic.html')
def index():
db.thing.name.writable = True
form = Form(db.thing)
return dict(form=form)
Nota thas este código só será capaz de exibir um formulário, para processá-lo depois de apresentar, necessidades código adicional para ser adicionado, como veremos mais tarde. Este exemplo supõe que você criou o aplicativo da App andaimes, de modo que um generic.html já é criado para você.
A `` readable``, `` writable``, `` default``, `` update``, e atributos `` `` require`` de db. {Tabela}. {Campo} `` são objectos especiais de classe `` ThreadSafeVariable`` definido a `` threadsafevariable`` módulo. Esses objetos são muito parecidos com Python rosca objetos locais, mas eles estão em todos os pedidos utilizando o valor fora da ação especificada inicializado-re. Isto significa que as ações podem mudar com segurança os valores desses atributos.
Fixtures personalizados¶
Um fixture é um objecto com a seguinte estrutura mínima:
from py4web import Fixture
class MyFixture(Fixture):
def on_request(self): pass
def on_success(self): pass
def on_error(self): pass
def transform(self, data): return data
If an action uses this fixture:
@action('index')
@action.uses(MyFixture())
def index(): return 'hello world'
then:
the
on_request()
function is guaranteed to be called before theindex()
function is calledthe
on_success()
function is guaranteed to be called if theindex()
function returns successfully or raisesHTTP
or performs aredirect
the
on_error()
function is guaranteed to be called when theindex()
function raises any exception other thanHTTP
.the
transform
function is called to perform any desired transformation of the value returned by theindex()
function.
auth and auth.user fixture¶
`` Auth`` e `` auth.user`` são ambos os jogos. Eles dependem de `` session``. O papel do acesso é fornecer a ação com informações de autenticação. Ele é utilizado como segue:
from py4web import action, redirect, Session, DAL, URL
from py4web.utils.auth import Auth
import os
session = Session(secret='my secret key')
DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
db = DAL('sqlite://storage.db', folder=DB_FOLDER, pool_size=1)
auth = Auth(session, db)
auth.enable()
@action('index')
@action.uses(auth)
def index():
user = auth.get_user() or redirect(URL('auth/login'))
return 'Welcome %s' % user.get('first_name')
O construtor do objeto `` Auth`` define a tabela `` auth_user`` com os seguintes campos: nome de usuário, e-mail, senha, first_name, last_name, sso_id e action_token (os dois últimos são principalmente para uso interno).
`` Auth.enable () `` registadoras múltiplas acções incluindo `` {nomeaplic} / auth / login`` e que exige a presença de a `` auth.html`` molde e a `` auth`` componente valor fornecido pela o `` aplicativo _scaffold``.
O objeto `` auth`` é a fixure. Ele gerencia as informações do usuário. Ela expõe um único método:
auth.get_user()
which returns a python dictionary containing the information of the
currently logged in user. If the user is not logged-in, it returns
None
and in this case the code of the example redirects to the
auth/login
page.
Desde essa verificação é muito comum, py4web fornece um fixture adicional `` auth.user``:
@action('index')
@action.uses(auth.user)
def index():
user = auth.get_user()
return 'Welcome %s' % user.get('first_name')
Este fixture redireciona automaticamente para a página login`` `` auth / se o usuário não está conectado. Depende de `` auth``, que depende `` db`` e `` session``.
The auth
fixture is plugin based and supports multiple plugin
methods. They include OAuth2 (Google, Facebook, Twitter), PAM, LDAP, and
SMAL2.
Here is an example of using the Google OAuth2 plugin:
from py4web.utils.auth_plugins.oauth2google import OAuth2Google
auth.register_plugin(OAuth2Google(
client_id='...',
client_secret='...',
callback_url='auth/plugin/oauth2google/callback'))
The client_id
and client_secret
are provided by Google. The
callback url is the default option for py4web and it must be whitelisted
with Google.
All auth
plugins are objects. Different plugins are
configured in different ways but they are registered using
auth.register_plugin(...)
. Examples are provided in
_scaffold/common.py
.
Caching e Memoize¶
py4web provides a cache in RAM object that implements the last recently used (LRU) algorithm. It can be used to cache any function via a decorator:
import uuid
from py4web import Cache, action
cache = Cache(size=1000)
@action('hello/<name>')
@cache.memoize(expiration=60)
def hello(name):
return "Hello %s your code is %s" % (name, uuid.uuid4())
It will cache (memoize) the return value of the hello
function, as
function of the input name
, for up to 60 seconds. It will store in
cache the 1000 most recently used values. The data is always stored in
RAM.
The cache
object is not a fixture and it should not and cannot be
registered using the @action.uses
decorator but we mention it here
because some of the fixtures use this object internally. For example,
template files are cached in RAM to avoid accessing the file system
every time a template needs to be rendered.
Decoradores de conveniência¶
The _scaffold
application, in common.py
defines two special
convenience decorators:
@unauthenticated
def index():
return dict()
e
@authenticated
def index():
return dict()
They apply all of the decorators below, use a template with the same name as the function (.html), and also register a route with the name of action followed by the number of arguments of the action separated by a slash (/).
@unauthenticated não requer que o usuário seja identificado. @authenticated necessário que o usuário estar logado.
They can be combined with (and precede) other @action.uses(...)
but
they should not be combined with @action(...)
because they perform
that function automatically.