Exemplos de uso SqlAlchemy
Lembrando que ja temos no blog um tutorial completo de uso do SqlAlchemy
A distribuição SQLAlchemy inclui uma variedade de exemplos de código que ilustram um conjunto selecionado de padrões, alguns típicos e outros não. Todos são executáveis e podem ser encontrados no
/examples
diretório da distribuição. Descrições e código fonte de todos podem ser encontrados aqui.
Exemplos adicionais de SQLAlchemy, com contribuição de alguns usuários, estão disponíveis no wiki em http://www.sqlalchemy.org/trac/wiki/UsageRecipes .
Mapeando Receitas
Lista de adjacências
Um exemplo de uma estrutura de dicionário de dicionários mapeada usando um modelo de lista de adjacência.Ex .:
node = TreeNode('rootnode')
node.append('node1')
node.append('node3')
session.add(node)
session.commit()
dump_tree(node)
Associações
Exemplos que ilustram o uso do padrão "objeto de associação", em que uma classe intermediária medeia o relacionamento entre duas classes que estão associadas em um padrão de muitos para muitos.Listagem de arquivos:
proxied_association.py - O mesmo exemplo que basic_association, adicionando o uso desqlalchemy.ext.associationproxy
para fazer referências explícitas aOrderItem
opcional.
basic_association.py - Ilustre um relacionamento muitos para muitos entre um "Pedido" e uma coleção de objetos "Item", associando um preço de compra a cada um deles por meio de um objeto de associação chamado "OrderItem"
dict_of_sets_with_default.py - Um exemplo avançado de proxy de associação que ilustra o aninhamento de proxies de associação para produzir coleções Python de vários níveis, neste caso, um dicionário com chaves de seqüência de caracteres e conjuntos de números inteiros como valores, que ocultam as classes mapeadas subjacentes.
Gráficos direcionados
Um exemplo de persistência para uma estrutura de gráfico direcionado. O gráfico é armazenado como uma coleção de arestas, cada uma referenciando um nó "inferior" e um "superior" em uma tabela de nós. A persistência básica e a consulta de vizinhos inferiores e superiores são ilustradas:n2 = Node(2)
n5 = Node(5)
n2.add_neighbor(n5)
print n2.higher_neighbors()
Relações dinâmicas como dicionários
Ilustra como colocar uma fachada semelhante a um dicionário em cima de uma relação "dinâmica", para que as operações do dicionário (assumindo simples teclas de seqüência de caracteres) possam operar em uma grande coleção sem carregar a coleção completa de uma só vez.Listagem de arquivos:
Associações genéricas
Ilustra vários métodos de associação de vários tipos de pais a um objeto filho específico.Todos os exemplos usam a extensão declarativa junto com mixins declarativos. Cada uma apresenta o caso de uso idêntico no final - duas classes
Customer
e Supplier
, ambas subclassificando o HasAddresses
mixin, o que garante que a classe pai seja fornecida com uma addresses
coleção que contenha Address
objetos.Os scripts discriminator_on_association.py e generic_fk.py são versões modernizadas de receitas apresentadas na postagem de blog de 2007 Polymorphic Associations with SQLAlchemy .
Listagem de arquivos:
table_per_association.py - ilustra um mixin que fornece uma associação genérica por meio de tabelas de associação geradas individualmente para cada classe pai. Os próprios objetos associados são mantidos em uma única tabela compartilhada entre todos os pais.
table_per_related.py - ilustra uma associação genérica que persiste objetos de associação em tabelas individuais, cada uma gerada para persistir esses objetos em nome de uma classe pai específica.
discriminator_on_association.py - Ilustra um mixin que fornece uma associação genérica usando uma única tabela de destino e uma única tabela de associação, referida por todas as tabelas pai. A tabela de associação contém uma coluna "discriminador" que determina que tipo de objeto pai se associa a cada linha específica na tabela de associação.
generic_fk.py - Ilustra a chamada “chave estrangeira genérica”, de maneira semelhante à de estruturas populares como Django, ROR etc. Essa abordagem ignora as práticas de integridade referencial padrão, pois a coluna “chave estrangeira” não é realmente restrito a se referir a qualquer tabela específica; em vez disso, a lógica no aplicativo é usada para determinar qual tabela é referenciada.
Coleções grandes
Exemplo de coleção grande.Ilustra as opções a serem usadas
relationship()
quando a lista de objetos relacionados é muito grande, incluindo:- Relacionamentos “dinâmicos” que consultam fatias de dados acessadas
- como usar o ON DELETE CASCADE em conjunto com
passive_deletes=True
para melhorar significativamente o desempenho da exclusão de coleção relacionada.
Caminhos materializados
Ilustra o padrão de "caminhos materializados" para dados hierárquicos usando o SQLAlchemy ORM.Listagem de arquivos:
materialized_paths.py - Ilustra o padrão de "caminhos materializados".
Conjuntos aninhados
Ilustra uma maneira rudimentar de implementar o padrão de "conjuntos aninhados" para dados hierárquicos usando o SQLAlchemy ORM.Listagem de arquivos:
nested_sets.py - Estrutura em árvore dos "conjuntos aninhados" da Celko.
Desempenho
Um conjunto de perfis de desempenho para uma variedade de casos de uso SQLAlchemy.Cada conjunto se concentra em um caso de uso específico com um perfil de desempenho específico e implicações associadas:
- inserções em massa
- inserções individuais, com ou sem transações
- buscando um grande número de linhas
- executando muitas consultas curtas
Uma ferramenta de linha de comando é apresentada no nível do pacote que permite que conjuntos individuais sejam executados:
$ python -m examples.performance --help
usage: python -m examples.performance [-h] [--test TEST] [--dburl DBURL]
[--num NUM] [--profile] [--dump]
[--runsnake] [--echo]
{bulk_inserts,large_resultsets,single_inserts}
positional arguments:
{bulk_inserts,large_resultsets,single_inserts}
suite to run
optional arguments:
-h, --help show this help message and exit
--test TEST run specific test name
--dburl DBURL database URL, default sqlite:///profile.db
--num NUM Number of iterations/items/etc for tests;
default is module-specific
--profile run profiling and dump call counts
--dump dump full call profile (implies --profile)
--runsnake invoke runsnakerun (implies --profile)
--echo Echo SQL output
$ python -m examples.performance bulk_inserts
$ python -m examples.performance bulk_inserts \
--dburl mysql+mysqldb://scott:tiger@localhost/test \
--profile --num 1000
Listagem de arquivos
Listagem de arquivos:
bulk_updates.py - Essa série de testes ilustra maneiras diferentes de atualizar um grande número de linhas em massa.
large_resultsets.py - Nesta série de testes, estamos aguardando o momento para carregar um grande número de linhas muito pequenas e simples.
bulk_inserts.py - Esta série de testes ilustra maneiras diferentes de INSERIR um grande número de linhas em massa.
short_selects.py - Esta série de testes ilustra maneiras diferentes de selecionar um único registro pela chave primária
single_inserts.py - Nesta série de testes, estamos analisando um método que insere uma linha em uma transação distinta e, posteriormente, retorna ao estado essencialmente "fechado". Isso seria análogo a uma chamada de API que inicia uma conexão com o banco de dados, insere a linha, confirma e fecha.
__main__.py - Permite que o pacote de exemplos / desempenho seja executado como um script.
Executando todos os testes com o tempo
Esta é a forma padrão de execução:$ python -m examples.performance single_inserts
Tests to run: test_orm_commit, test_bulk_save,
test_bulk_insert_dictionaries, test_core,
test_core_query_caching, test_dbapi_raw_w_connect,
test_dbapi_raw_w_pool
test_orm_commit : Individual INSERT/COMMIT pairs via the
ORM (10000 iterations); total time 13.690218 sec
test_bulk_save : Individual INSERT/COMMIT pairs using
the "bulk" API (10000 iterations); total time 11.290371 sec
test_bulk_insert_dictionaries : Individual INSERT/COMMIT pairs using
the "bulk" API with dictionaries (10000 iterations);
total time 10.814626 sec
test_core : Individual INSERT/COMMIT pairs using Core.
(10000 iterations); total time 9.665620 sec
test_core_query_caching : Individual INSERT/COMMIT pairs using Core
with query caching (10000 iterations); total time 9.209010 sec
test_dbapi_raw_w_connect : Individual INSERT/COMMIT pairs w/ DBAPI +
connection each time (10000 iterations); total time 9.551103 sec
test_dbapi_raw_w_pool : Individual INSERT/COMMIT pairs w/ DBAPI +
connection pool (10000 iterations); total time 8.001813 sec
Despejando perfis para testes individuais
Uma saída de perfil Python pode ser descarregada para todos os testes ou, geralmente, para testes individuais:$ python -m examples.performance single_inserts --test test_core --num 1000 --dump
Tests to run: test_core
test_core : Individual INSERT/COMMIT pairs using Core. (1000 iterations); total fn calls 186109
186109 function calls (186102 primitive calls) in 1.089 seconds
Ordered by: internal time, call count
ncalls tottime percall cumtime percall filename:lineno(function)
1000 0.634 0.001 0.634 0.001 {method 'commit' of 'sqlite3.Connection' objects}
1000 0.154 0.000 0.154 0.000 {method 'execute' of 'sqlite3.Cursor' objects}
1000 0.021 0.000 0.074 0.000 /Users/classic/dev/sqlalchemy/lib/sqlalchemy/sql/compiler.py:1950(_get_colparams)
1000 0.015 0.000 0.034 0.000 /Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/default.py:503(_init_compiled)
1 0.012 0.012 1.091 1.091 examples/performance/single_inserts.py:79(test_core)
...
Usando RunSnake
Esta opção requer que a ferramenta de linha de comando RunSnake esteja instalada:$ python -m examples.performance single_inserts --test test_core --num 1000 --runsnake
Escrevendo suas próprias suítes
O sistema do conjunto de profiler é extensível e pode ser aplicado ao seu próprio conjunto de testes. Essa é uma técnica valiosa a ser usada na decisão da abordagem adequada para algum conjunto de rotinas críticas ao desempenho. Por exemplo, se quisermos criar um perfil da diferença entre vários tipos de carregamento, podemos criar um arquivotest_loads.py
com o seguinte conteúdo:from examples.performance import Profiler
from sqlalchemy import Integer, Column, create_engine, ForeignKey
from sqlalchemy.orm import relationship, joinedload, subqueryload, Session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = None
session = None
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
# Init with name of file, default number of items
Profiler.init("test_loads", 1000)
@Profiler.setup_once
def setup_once(dburl, echo, num):
"setup once. create an engine, insert fixture data"
global engine
engine = create_engine(dburl, echo=echo)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
sess = Session(engine)
sess.add_all([
Parent(children=[Child() for j in range(100)])
for i in range(num)
])
sess.commit()
@Profiler.setup
def setup(dburl, echo, num):
"setup per test. create a new Session."
global session
session = Session(engine)
# pre-connect so this part isn't profiled (if we choose)
session.connection()
@Profiler.profile
def test_lazyload(n):
"load everything, no eager loading."
for parent in session.query(Parent):
parent.children
@Profiler.profile
def test_joinedload(n):
"load everything, joined eager loading."
for parent in session.query(Parent).options(joinedload("children")):
parent.children
@Profiler.profile
def test_subqueryload(n):
"load everything, subquery eager loading."
for parent in session.query(Parent).options(subqueryload("children")):
parent.children
if __name__ == '__main__':
Profiler.main()
$ python test_loads.py --dburl postgresql+psycopg2://scott:tiger@localhost/test
Running setup once...
Tests to run: test_lazyload, test_joinedload, test_subqueryload
test_lazyload : load everything, no eager loading. (1000 iterations); total time 11.971159 sec
test_joinedload : load everything, joined eager loading. (1000 iterations); total time 2.754592 sec
test_subqueryload : load everything, subquery eager loading. (1000 iterations); total time 2.977696 sec
$ python test_loads.py --num 100 --runsnake --test test_joinedload
Condições de junção de relacionamento
Exemplos de váriasorm.relationship()
configurações, que fazem uso do primaryjoin
argumento para compor tipos especiais de condições de junção.Listagem de arquivos:
threeway.py - Ilustre uma “junção de três vias” - onde uma tabela principal se une a uma tabela remota por meio de uma tabela de associação, mas a tabela principal também precisa se referir a algumas colunas diretamente na tabela remota.
cast.py - Ilustre umrelationship()
que une duas colunas em que essas colunas não são do mesmo tipo e um CAST deve ser usado no lado do SQL para correspondê-las.
Space Invaders
Um jogo Space Invaders usando SQLite como máquina de estado.Originalmente desenvolvido em 2012. Adaptado para trabalhar em Python 3.
É executado em um console de texto usando arte ASCII.
Para correr:
python -m examples.space_invaders.space_invaders
tail -f space_invaders.log
Listagem de arquivos:
XML Persistência
Ilustra três estratégias para persistir e consultar documentos XML, conforme representado pelo ElementTree em um banco de dados relacional. As técnicas não aplicam nenhum mapeamento aos objetos ElementTree diretamente, portanto, são compatíveis com o cElementTree nativo e com o lxml e podem ser adaptados para se adequar a qualquer tipo de sistema de representação DOM. A consulta ao longo de seqüências semelhantes a xpath também é ilustrada.Ex .:
# parse an XML file and persist in the database
doc = ElementTree.parse("test.xml")
session.add(Document(file, doc))
session.commit()
# locate documents with a certain path/attribute structure
for document in find_document('/somefile/header/field2[@attr=foo]'):
# dump the XML
print document
pickle_type.py - ilustra uma maneira rápida e suja de manter um documento XML expresso usando ElementTree e pickle.
adjacency_list.py - ilustra uma maneira explícita de manter um documento XML expresso usando o ElementTree.
optimized_al.py - Usa a mesma estratégia deadjacency_list.py
, mas associa cada linha do DOM à sua própria linha de documento, para que um documento completo dos nós do DOM possa ser carregado usando consultas O (1) - a construção da "hierarquia" é executada após o carregar de forma não recursiva e é mais eficiente.
Versionamento Objects
Controle de versão com uma tabela de histórico
Ilustra uma extensão que cria tabelas de versões para entidades e armazena registros para cada alteração. As extensões fornecidas geram uma classe "history" anônima que representa versões históricas do objeto de destino.Compare com os exemplos de controle de versão usando Linhas Temporais que gravam atualizações como novas linhas na mesma tabela, sem usar uma tabela de histórico separada.
O uso é ilustrado através de um módulo de teste de unidade
test_versioning.py
, que pode ser executado através de py.test
:# assume SQLAlchemy is installed where py.test is
cd examples/versioned_history
py.test test_versioning.py
from history_meta import Versioned, versioned_session
Base = declarative_base()
class SomeClass(Versioned, Base):
__tablename__ = 'sometable'
id = Column(Integer, primary_key=True)
name = Column(String(50))
def __eq__(self, other):
assert type(other) is SomeClass and other.id == self.id
Session = sessionmaker(bind=engine)
versioned_session(Session)
sess = Session()
sc = SomeClass(name='sc1')
sess.add(sc)
sess.commit()
sc.name = 'sc1modified'
sess.commit()
assert sc.version == 2
SomeClassHistory = SomeClass.__history_mapper__.class_
assert sess.query(SomeClassHistory).\
filter(SomeClassHistory.version == 1).\
all() \
== [SomeClassHistory(version=1, name='sc1')]
Versioned
mixin é projetado para funcionar com declarativo. Para usar a extensão com mapeadores clássicos, a _history_mapper
função pode ser aplicada:from history_meta import _history_mapper
m = mapper(SomeClass, sometable)
_history_mapper(m)
SomeHistoryClass = SomeClass.__history_mapper__.class_
test_versioning.py - Testes de unidade que ilustram o uso dashistory_meta.py
funções do módulo.
history_meta.py - Classe de mixagem com versão e outros utilitários.
Controle de versão usando linhas temporais
Vários exemplos que ilustram a técnica de interceptar alterações que seriam primeiro interpretadas como um UPDATE em uma linha e, em vez disso, transformá-lo em um INSERT de uma nova linha, deixando a linha anterior intacta como uma versão histórica.Compare com o exemplo Controle de versão com uma tabela de históricoque grava uma linha do histórico em uma tabela de histórico separada.
Listagem de arquivos:
versioned_rows.py - ilustra um método para interceptar alterações em objetos, transformando uma instrução UPDATE em uma única linha em uma instrução INSERT, para que uma nova linha seja inserida com os novos dados, mantendo a linha antiga intacta.
versioned_rows_w_versionid.py - ilustra um método para interceptar alterações em objetos, transformando uma instrução UPDATE em uma única linha em uma instrução INSERT, para que uma nova linha seja inserida com os novos dados, mantendo a linha antiga intacta.
versioned_map.py - Uma variante do exemplo versioned_rows criada em torno do conceito de uma estrutura de "tabela vertical", como as ilustradas nos exemplos do Vertical Attribute Mapping .
versioned_update_old_row.py - Ilustra a mesma técnica UPDATE into INSERT deversioned_rows.py
, mas também emite uma UPDATE na linha antiga para afetar uma alteração no carimbo de data / hora. Também inclui umQueryEvents.before_compile()
gancho para limitar as consultas apenas à versão mais recente.
Mapeamento Vertical de Atributos
Ilustra os mapeamentos da “tabela vertical”.Uma “tabela vertical” refere-se a uma técnica em que atributos individuais de um objeto são armazenados como linhas distintas em uma tabela. A técnica “tabela vertical” é usada para persistir objetos que podem ter um conjunto variado de atributos, às custas do controle e da concisão simples da consulta. É comumente encontrado em sistemas de gerenciamento de conteúdo / documento para representar estruturas criadas pelo usuário de maneira flexível.
Duas variantes na abordagem são dadas. Na segunda, cada linha faz referência a um "tipo de dados" que contém informações sobre o tipo de informação armazenada no atributo, como número inteiro, sequência ou data.
Exemplo:
shrew = Animal(u'shrew')
shrew[u'cuteness'] = 5
shrew[u'weasel-like'] = False
shrew[u'poisonous'] = True
session.add(shrew)
session.flush()
q = (session.query(Animal).
filter(Animal.facts.any(
and_(AnimalFact.key == u'weasel-like',
AnimalFact.value == True))))
print 'weasel-like animals', q.all()
dictlike-polymorphic.py - Mapeando uma tabela vertical com valor polimórfico como um dicionário.
dictlike.py - Mapeando uma tabela vertical como um dicionário.
Receitas de mapeamento de herança
Mapeamentos básicos de herança
Exemplos de trabalho de herança de tabela única, tabela unida e tabela de concreto, conforme descrito em Hierarquias de herança de classe de mapeamento .Listagem de arquivos:
join.py - Exemplo de herança de tabela unida (tabela por subclasse).
concrete.py - Betão-tabela (tabela-per-classe) exemplo herança.
single.py - Exemplo de herança de tabela única (tabela por hierarquia).
APIs especiais
Instrumentação de Atributos
Exemplos que ilustram modificações no sistema de gerenciamento de atributos do SQLAlchemy.Listagem de arquivos:
listen_for_events.py - ilustra como anexar eventos a todos os atributos instrumentados e ouvir eventos de alteração.
active_column_defaults.py - ilustra o uso doAttributeEvents.init_scalar()
evento, em conjunto com os padrões da coluna Core, para fornecer objetos ORM que produzem automaticamente o valor padrão quando um atributo não definido é acessado.
custom_management.py - ilustra a instrumentação de classe personalizada, usando osqlalchemy.ext.instrumentation
pacote de extensão.
Sharding Horizontal
Um exemplo básico de uso da API SQLAlchemy Sharding. O sharding refere-se à escala horizontal de dados em vários bancos de dados.Os componentes básicos de um mapeamento "fragmentado" são:
- vários bancos de dados, cada um com um 'shard id'
- uma função que pode retornar um único ID de fragmento, dada uma instância a ser salva; isso é chamado de "shard_chooser"
- uma função que pode retornar uma lista de IDs de shard que se aplicam a um identificador de instância específico; isso é chamado de "id_chooser". Se retornar todos os IDs de shard, todos os shards serão pesquisados.
- uma função que pode retornar uma lista de IDs de shard para tentar, dada uma consulta específica ("query_chooser"). Se retornar todos os IDs de shard, todos os shards serão consultados e os resultados serão reunidos.
A construção de rotinas genéricas de fragmentação é uma abordagem ambiciosa para a questão da organização de instâncias entre vários bancos de dados. Para uma alternativa mais clara, a abordagem "entidade distinta" é um método simples de atribuir objetos a diferentes tabelas (e potencialmente nós do banco de dados) de maneira explícita - descrita no wiki em EntityName .
Listagem de arquivos:
Estendendo o ORM
Cache de Dogpile
Ilustra como incorporar a funcionalidade dogpile.cache aoQuery
objeto, permitindo o controle total do cache, bem como a capacidade de extrair atributos de "carregamento lento" do cache de longo prazo.Nesta demonstração, as seguintes técnicas são ilustradas:
- Usando subclasses personalizadas de
Query
- Técnica básica de contornar a consulta para extrair de uma fonte de cache personalizada em vez do banco de dados.
- Cache rudimentar com dogpile.cache, usando "regiões" que permitem controle global sobre um conjunto fixo de configurações.
- Usando
MapperOption
objetos personalizados para configurar opções em uma Consulta, incluindo a capacidade de chamar as opções em profundidade em um gráfico de objeto quando ocorrem cargas preguiçosas.
# query for Person objects, specifying cache
q = Session.query(Person).options(FromCache("default"))
# specify that each Person's "addresses" collection comes from
# cache too
q = q.options(RelationshipCache(Person.addresses, "default"))
# query
print q.all()
Os próprios scripts de demonstração, em ordem de complexidade, são executados como módulos Python, para que as importações relativas funcionem:
python -m examples.dogpile_caching.helloworld
python -m examples.dogpile_caching.relationship_caching
python -m examples.dogpile_caching.advanced
python -m examples.dogpile_caching.local_session_caching
environment.py - Estabeleça caminhos e configurações de arquivo de dados / cache, dados do dispositivo de inicialização, se necessário.
caching_query.py - Representa funções e classes que permitem o uso do armazenamento em cache Dogpile com SQLAlchemy. Apresenta uma opção de consulta chamada FromCache.
model.py - O modelo de dados, que representa a Pessoa que possui vários objetos de Endereço, cada um com Código Postal, Cidade, País.
fixture_data.py - Instala alguns dados de amostra. Aqui temos alguns códigos postais para algumas cidades dos EUA / Canadá. Em seguida, 100 registros de Pessoa são instalados, cada um com um código postal selecionado aleatoriamente.
helloworld.py - Ilustre como carregar alguns dados e armazenar em cache os resultados.
relationship_caching.py - Ilustra como adicionar opções de cache nos pontos de extremidade do relacionamento, para que o lazyload seja carregado do cache.
advanced.py - Ilustra o uso de Query combinado com a opção FromCache, incluindo carregamento de front-end, invalidação de cache e armazenamento em cache de coleção.
local_session_caching.py - Este exemplo cria um novo backend dogpile.cache que manterá os dados em um dicionário local da sessão atual. remove () a sessão e o cache se foi.
Integração PostGIS
Um exemplo ingênuo que ilustra técnicas para ajudar a incorporar a funcionalidade PostGIS.Este exemplo foi desenvolvido originalmente na esperança de que fosse extrapolado para uma camada abrangente de integração do PostGIS. Temos o prazer de anunciar que isso se concretizou como GeoAlquimia .
O exemplo ilustra:
- uma extensão DDL que permite que CREATE / DROP funcione em conjunto com AddGeometryColumn / DropGeometryColumn
- um tipo de geometria, além de alguns subtipos, que convertem os valores da linha de resultado em um objeto que reconhece GIS e também se integra à extensão DDL.
- um objeto que reconhece GIS que armazena um valor de geometria bruta e fornece uma fábrica para funções como AsText ().
- um comparador ORM que pode substituir métodos de coluna padrão em objetos mapeados para produzir operadores GIS.
- um ouvinte de evento de atributo que intercepta cadeias e converte em GeomFromText ().
- um exemplo de operador independente.
Ex .:
print session.query(Road).filter(
Road.road_geom.intersects(r1.road_geom)).all()
0 Comentários