Categorías
Python

Administración de transacciones con Django 1.6

 

Tabla de Contenidos

  • Qué es una transacción? mal de
  • Lo que con la gestión de transacciones antes de Django 1.6? DatabasesClient LibrariesDjangoWhat significa esto?
  • bases de datos de clientes
  • Bibliotecas
  • Django
  • ¿Qué significa esto? derecho de
  • ¿Qué pasa con la gestión de transacciones en Django 1.6? Ejemplo
  • raya
  • TransactionsThe recomienda wayUsing un decoratorTransaction por HTTP Request
  • El recomendada manera
  • El uso de un decorador de
  • Transacción por petición HTTP Transacciones
  • savepoints
  • anidadas
  • Conclusión
  • Bases de datos
  • cliente Bibliotecas
  • Django
  • ¿Qué significa esto?
  • El modo recomendado
  • El uso de un decorador de
  • Transacción por HTTP Request

Si alguna vez dedicado mucho tiempo a la gestión de transacciones de base de datos de Django, sabes lo confuso que puede conseguir. En el pasado, la documentación que se proporciona un poco de profundidad, pero la comprensión sólo se llegó a través de la construcción y experimentación.

Había una gran cantidad de decoradores de trabajar, como commit_on_success, commit_manually, commit_unless_managed, rollback_unless_managed, enter_transaction_management, leave_transaction_management, sólo para nombrar unos pocos. Afortunadamente, con Django 1.6 que todo vaya por la puerta. Que realmente necesita sólo para saber acerca de un par de funciones que ahora. Y vamos a llegar a aquellos en sólo un segundo. En primer lugar, vamos a abordar estos temas:

  • ¿Qué es la gestión de transacciones? mal de
  • Lo que con la gestión de transacciones antes de Django 1.6?

Antes de saltar en:

de

  • ¿Qué derecho sobre la gestión de transacciones en Django 1.6?

Y entonces se trata de un ejemplo detallado: Transacciones

  • raya Ejemplo
  • El modo recomendado
  • El uso de un decorador de
  • Transacción por petición HTTP Transacciones
  • savepoints
  • anidadas

Qué es una transacción?

Según SQL-92, “An SQL-transacción (a veces llamado simplemente un‘transacción’) es una secuencia de ejecuciones de SQL-declaraciones que es atómica con respecto a la recuperación”. En otras palabras, todas las sentencias SQL se ejecutan y se comprometieron juntos. Del mismo modo, cuando se deshace, todas las declaraciones consiguen rodaron de nuevo juntos.

Por ejemplo:

# START
note = Note(title="my first note", text="Yay!")
note = Note(title="my second note", text="Whee!")
address1.save()
address2.save()
# COMMIT

Así que una transacción es una sola unidad de trabajo en una base de datos. Y que una sola unidad de trabajo está delimitada por una transacción de salida y luego una confirmación o una retrotracción explícita. mal de

Lo que con la gestión de transacciones antes de Django 1.6?

Con el fin de responder plenamente a esta pregunta, debemos abordar cómo las transacciones se tratan en la base de datos, bibliotecas de cliente, y dentro de Django.

Bases de datos

Cada declaración en una base de datos tiene que correr en una transacción, incluso si la transacción incluye sólo una declaración. La mayoría de las bases de datos

tienen una configuración de confirmación automática, que por lo general se establece en True como un defecto. Este AUTOCOMMIT envuelve cada declaración en una transacción que se confirma de inmediato si la sentencia es satisfactoria. Por supuesto se puede llamar manualmente a algo así como START_TRANSACTION que suspender temporalmente la AUTOCOMMIT hasta que llame COMMIT_TRANSACTION o ROLLBACK.

Sin embargo, el llevar aquí es que el ajuste AUTOCOMMIT aplica una implícita confirmación después de cada declaración .

cliente Bibliotecas

luego está el cliente Python bibliotecas como sqlite3 y MySQLdb, que permiten a los programas de Python para la interfaz con las mismas bases de datos. Dichas bibliotecas siguen un conjunto de normas para la forma de acceso y consulta de las bases de datos. Esa norma, DB API 2.0, se describe en el PEP 249. Si bien puede hacer para leer un poco un poco seco, una toma de distancia importante es que PEP 249 indica que la base de datos debe ser AUTOCOMMIT OFF por defecto.

Esto está claramente en contradicción con lo que está sucediendo dentro de la base de datos: declaraciones

  • SQL siempre se tienen que ejecutar en una transacción, lo que abre la base de datos general para usted a través de AUTOCOMMIT.
  • Sin embargo, de acuerdo con PEP 249, esto no debería ocurrir. bibliotecas
  • cliente deben reflejar lo que sucede dentro de la base de datos, pero ya que no están autorizados a girar de confirmación automática por defecto, simplemente envuelva las sentencias SQL en una transacción, al igual que la base de datos.

bien. Quédate conmigo un poco más.

Django

Introduzca Django . Django también tiene algo que decir acerca de la gestión de transacciones. En Django 1.5 y anteriores, Django básicamente corrió con una transacción abierta y auto-cometió esa transacción cuando escribió a los datos de la base de datos. Así que cada vez que se llama algo así como model.save () o model.update (), Django genera las sentencias SQL apropiadas y confirma la transacción.

También en Django 1.5 y anteriores, se recomendó que utilizó el TransactionMiddleware a las transacciones se unen a las peticiones HTTP. Cada solicitud se le dio una transacción. Si la respuesta devuelta sin ninguna excepción, Django confirmar la transacción, pero si su función de vista arrojó un error, ROLLBACK se llama. Esto, en efecto, apagó AUTOCOMMIT. Si quería estándar, el estilo de gestión de transacciones de confirmación automática nivel de base de datos que ha tenido que gestionar las transacciones usted mismo – por lo general mediante el uso de un decorador transacción en su función de vista como @ transaction.commit_manually, o @ transaction.commit_on_success.

Tome una respiración. O dos.

¿Qué significa esto?

Sí, hay mucho que hacer allí, y resulta que la mayoría de los desarrolladores sólo quieren los autocommits estándar de nivel de base de datos – las transacciones que significa quedarse detrás de las escenas, haciendo su cosa, hasta que tenga que ajustar manualmente. derecho de

¿Qué pasa con la gestión de transacciones en Django 1.6?

, recepción a Django 1.6. Haga todo lo posible para olvidar todo lo que acabamos de hablar y simplemente recordamos que en Django 1.6, se utiliza la base de datos AUTOCOMMIT y gestionar transacciones manualmente cuando sea necesario. Esencialmente, tenemos un modelo mucho más sencillo que hace básicamente lo que la base de datos fue diseñado para hacer en primer lugar. La teoría

suficiente. Código de let.

raya Ejemplo

Aquí tenemos este ejemplo función de vista que los mangos de registro de un usuario y llamando a raya para el procesamiento de tarjetas de crédito.

def register(request):
user = None
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():

customer = Customer.create("subscription",
email = form.cleaned_data['email'],
description = form.cleaned_data['name'],
card = form.cleaned_data['stripe_token'],
plan="gold",
)

cd = form.cleaned_data
try:
user = User.create(cd['name'], cd['email'], cd['password'],
cd['last_4_digits'])

if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()

except IntegrityError:
form.addError(cd['email'] + ' is already a member')
else:
request.session['user'] = user.pk
return HttpResponseRedirect('/')

else:
form = UserForm()

return render_to_response(
'register.html',
{
'form': form,
'months': range(1, 12),
'publishable': settings.STRIPE_PUBLISHABLE,
'soon': soon(),
'user': user,
'years': range(2011, 2036),
},
context_instance=RequestContext(request)
)

este punto de vista primeras llamadas Customer.create que en realidad pide raya para manejar el procesamiento de tarjetas de crédito. Entonces se crea un nuevo usuario. Si conseguimos una respuesta de vuelta a rayas que se actualice el cliente recién creado con el stripe_id. Si no conseguimos una vuelta al cliente (Stripe es hacia abajo) vamos a añadir una entrada a la tabla UnpaidUsers con el correo electrónico de los clientes de nueva creación, por lo que podemos pedir que vuelva a intentar sus datos de la tarjeta de crédito más tarde.

La idea es que, incluso si la raya se ha reducido el usuario aún podrá registrarse y empezar a usar nuestro sitio. Nos limitaremos a preguntar de nuevo en una fecha posterior para la información de la tarjeta de crédito.

Entiendo que esto puede ser un poco de un ejemplo artificial, y no es la forma en que iba a poner en práctica dicha funcionalidad si tuviera que hacerlo, pero el propósito es demostrar transacciones.

adelante. Pensando en las transacciones, y teniendo en cuenta que por defecto Django 1.6 da comportamiento nos la confirmación automática para nuestra base de datos, vamos a ver el código relacionado con la base de datos un poco más.

cd = form.cleaned_data
try:
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])

if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()

except IntegrityError:
# ...

¿Se puede detectar cualquier problema? Bueno lo que pasa si los UnpaidUsers (email = cd [ ‘email). Save () línea de falla?

Tendrá un usuario, registrado en el sistema, que el sistema cree que se ha verificado su tarjeta de crédito, pero en realidad no ha verificado la tarjeta.

sólo queremos uno de dos resultados:

Lo que significa que queremos los dos estados separados de bases de datos a cualquiera de los dos o ambos cometen reversión. Un caso perfecto para la transacción humilde.

En primer lugar, vamos a escribir algunas pruebas para verificar las cosas se comportan de la manera que queremos que lo hagan.

@mock.patch('payments.models.UnpaidUsers.save', side_effect = IntegrityError)
def test_registering_user_when_strip_is_down_all_or_nothing(self, save_mock):

#create the request used to test the view
self.request.session = {}
self.request.method='POST'
self.request.POST = {'email' : 'python@rocks.com',
'name' : 'pyRock',
'stripe_token' : '...',
'last_4_digits' : '4242',
'password' : 'bad_password',
'ver_password' : 'bad_password',
}

#mock out stripe and ask it to throw a connection error
with mock.patch('stripe.Customer.create', side_effect =
socket.error("can't connect to stripe")) as stripe_mock:

#run the test
resp = register(self.request)

#assert there is no record in the database without stripe id.
users = User.objects.filter(email="python@rocks.com")
self.assertEquals(len(users), 0)

#check the associated table also didn't get updated
unpaid = UnpaidUsers.objects.filter(email="python@rocks.com")
self.assertEquals(len(unpaid), 0)

El decorador en la parte superior de la prueba es una maqueta que lanzar una ‘IntegrityError’ cuando tratamos de salvar a la mesa UnpaidUsers.

Se trata de responder a la pregunta: “¿Qué pasa si los UnpaidUsers (email = cd [ ‘email). Save () línea de falla?” El siguiente fragmento de código sólo crea una sesión burlado, con la información adecuada que necesitamos para nuestra función de registro. Y entonces el mock.patch con las fuerzas del sistema para creer que la raya se ha reducido … por fin llegamos a la prueba.

resp = register(self.request)

El encima de la línea sólo llama nuestra opinión registro que pasa función en la solicitud burlado. Entonces sólo una verificación para garantizar las tablas no se actualizan:

#assert there is no record in the database without stripe_id.
users = User.objects.filter(email="python@rocks.com")
self.assertEquals(len(users), 0)

#check the associated table also didn't get updated
unpaid = UnpaidUsers.objects.filter(email="python@rocks.com")
self.assertEquals(len(unpaid), 0)

lo tanto, debe fallar si se corre la prueba:

======================================================================
FAIL: test_registering_user_when_strip_is_down_all_or_nothing (tests.payments.testViews.RegisterPageTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/j1z0/.virtualenvs/django_1.6/lib/python2.7/site-packages/mock.py", line 1201, in patched
return func(*args, **keywargs)
File "/Users/j1z0/Code/RealPython/mvp_for_Adv_Python_Web_Bookests/paymentsestViews.py", line 266, in test_registering_user_when_strip_is_down_all_or_nothing
self.assertEquals(len(users), 0)
AssertionError: 1 != 0

----------------------------------------------------------------------

Niza. Parece gracioso que decir pero eso es exactamente lo que queríamos. Recuerde: estamos practicando TDD aquí. El mensaje de error nos dice que el usuario de hecho está siendo almacenada en la base de datos – que es exactamente lo que no queremos porque no paga!

Transacciones al rescate … Transacciones

En realidad, hay varias maneras de crear transacciones en Django 1.6. Ir de

de tolerancia de unos pocos.

El modo recomendado

De acuerdo con la documentación de Django 1.6:

“Django proporciona una única API para transacciones de bases de datos de control. […] La atomicidad es la característica definitoria de las transacciones de bases de datos. atómica nos permite crear un bloque de código dentro de la cual se garantiza la atomicidad en la base de datos. Si el bloque de código se completa con éxito, se confirman los cambios a la base de datos. Si hay una excepción, los cambios se deshacen “.

atómica puede ser utilizado como un decorador o como context_manager. Así que si lo usamos como un gestor de contexto, el código en nuestra función de registro se vería así:

from django.db import transaction

try:
with transaction.atomic():
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])

if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()

except IntegrityError:
form.addError(cd['email'] + ' is already a member')

Tenga en cuenta la línea con transaction.atomic (). Todo el código dentro de ese bloque se ejecuta dentro de una transacción. Así que si volver a ejecutar nuestras pruebas, todos ellos deben pasar! Recuerde que una transacción es una sola unidad de trabajo, por lo que todo el interior del gestor de contexto se puso de nuevo juntos cuando la llamada UnpaidUsers falla.

El uso de un decorador de

También podemos intentar añadir atómica como decorador.

@transaction.atomic():
def register(request):
# ...snip....

try:
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])

if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()

except IntegrityError:
form.addError(cd['email'] + ' is already a member')

Si volver a ejecutar nuestras pruebas, que se producirá un error con el mismo error que teníamos antes.

¿Por qué es eso? ¿Por qué no la parte posterior del rodillo de transacciones correctamente? La razón es porque transaction.atomic está buscando algún tipo de excepción y así, hemos capturado ese error (es decir, el IntegrityError en nuestro intento, excepto bloque), por lo transaction.atomic nunca lo vio y por lo tanto la funcionalidad estándar AUTOCOMMIT se hizo cargo. Pero

de eliminar el supuesto intento excepción hará que la excepción a ser arrojado a la cadena de llamadas y lo más probable estallar en otro lugar. Así que no podemos hacer eso.

Así que el truco es poner el atómica gestor de contexto dentro de la prueba, excepto bloque, que es lo que hicimos en nuestra primera solución. Mirando el código correcto:

from django.db import transaction

try:
with transaction.atomic():
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])

if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()

except IntegrityError:
form.addError(cd['email'] + ' is already a member')

Cuando UnpaidUsers dispara el IntegrityError el gestor de contexto transaction.atomic () se pondrá al día y realizar la reversión. En el momento en nuestros ejecuta código en el controlador de excepciones, (es decir, la línea form.addError) la reversión se hará de manera segura y que se podía hacer llamadas de base de datos si es necesario. También tenga en cuenta todas las llamadas bases de datos antes o después de que el gestor de contexto transaction.atomic () no se verán afectados, independientemente del resultado final del context_manager.

Transacción por HTTP Request

Django 1.6 (como 1.5) también le permite operar en un modo “por solicitud de transacción”. En este modo de Django se ajustará automáticamente la función de vista en una transacción. Si la función lanza una excepción Django deshacer la transacción, de lo contrario será confirmar la transacción.

conseguirlo configuración que tiene que fijar ATOMIC_REQUEST True en la configuración de base de datos para cada base de datos que desea tener este comportamiento. Así que en nuestro “settings.py” hacemos el cambio como éste:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'test.db'),
'ATOMIC_REQUEST': True,
}
}

En la práctica esto sólo se comporta exactamente como si se pone el decorador en nuestra función de vista. Por lo que no sirve a nuestros propósitos.

No obstante, es que vale la pena tener en cuenta que con las dos ATOMIC_REQUESTS y el decorador @ transaction.atomic es posible todavía coger / manejar esos errores después de que se lanzan desde el punto de vista. Con el fin de atrapar a los errores que tendría que poner en práctica algunas de middleware de encargo, o se puede anular urls.hadler500 o haciendo una plantilla 500.html.

savepoints

A pesar de que las transacciones son atómicos que se pueden desglosar en puntos de retorno. Piense en los puntos de rescate como transacciones parciales.

Así que si usted tiene una transacción que tiene cuatro sentencias SQL para completar, se puede crear un punto de almacenamiento después de la segunda declaración. Una vez que se crea que el punto de almacenamiento, incluso si el tercero o cuarto comunicado fallan se puede hacer una reversión parcial, la eliminación de la tercera y cuarta declaración, pero manteniendo los dos primeros.

Así que es básicamente como una división de transacción en transacciones ligeros más pequeños que le permite hacer restauraciones parciales o confirmaciones.

Pero tenga en cuenta si la transacción principal dónde hacerse deshace (tal vez debido a una IntegrityError que se planteó y no se detecta, entonces todos los puntos de retorno conseguirá revertido también).

Veamos un ejemplo de cómo funcionan los puntos de retorno.

@transaction.atomic()
def save_points(self,save=True):

user = User.create('jj','inception','jj','1234')
sp1 = transaction.savepoint()

user.name = 'starting down the rabbit hole'
user.stripe_id = 4
user.save()

if save:
transaction.savepoint_commit(sp1)
else:
transaction.savepoint_rollback(sp1)

Aquí la función entero está en una transacción. Después de crear un nuevo usuario se crea un punto de almacenamiento y obtener una referencia al punto de rescate. Los tres siguientes statements-

user.name = 'starting down the rabbit hole'
user.stripe_id = 4
user.save()

no -son parte del punto de almacenamiento existente, por lo que existe el riesgo de ser parte de la próxima savepoint_rollback o savepoint_commit. En el caso de un savepoint_rollback, el usuario de la línea = User.create ( ‘JJ’, ‘creación’, ‘JJ’, ‘1234’) todavía se comprometerán a la base de datos a pesar de que el resto de las actualizaciones no lo hará.

Dicho de otra manera, estas dos pruebas siguientes describen cómo el trabajo de puntos de retorno:

def test_savepoint_rollbacks(self):

self.save_points(False)

#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)

#note the values here are from the original create call
self.assertEquals(users[0].stripe_id, '')
self.assertEquals(users[0].name, 'jj')

def test_savepoint_commit(self):
self.save_points(True)

#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)

#note the values here are from the update calls
self.assertEquals(users[0].stripe_id, '4')
self.assertEquals(users[0].name, 'starting down the rabbit hole')

también después de confirmar o deshacer un punto de salvaguarda que puede seguir haciendo el trabajo en la misma transacción. Y que el trabajo no se verá afectado por el resultado del punto de rescate anterior.

Por ejemplo, si actualizamos nuestras save_points funcionan como tales:

@transaction.atomic()
def save_points(self,save=True):

user = User.create('jj','inception','jj','1234')
sp1 = transaction.savepoint()

user.name = 'starting down the rabbit hole'
user.save()

user.stripe_id = 4
user.save()

if save:
transaction.savepoint_commit(sp1)
else:
transaction.savepoint_rollback(sp1)

user.create('limbo','illbehere@forever','mind blown',
'1111')

Independientemente de si savepoint_commit o savepoint_rollback recibió el nombre de usuario ‘limbo’ todavía será creado con éxito. A menos que otra cosa hace que toda la transacción se deshace. Las transacciones anidadas

Además de los puntos de salvaguarda que especifican de forma manual, con el punto de almacenamiento (), savepoint_commit y savepoint_rollback, la creación de una transacción anidada se creará automáticamente un punto de salvaguarda para nosotros, y rodar de nuevo si conseguimos un error.

Extendiendo nuestro ejemplo un poco más lejos, tenemos:

@transaction.atomic()
def save_points(self,save=True):

user = User.create('jj','inception','jj','1234')
sp1 = transaction.savepoint()

user.name = 'starting down the rabbit hole'
user.save()

user.stripe_id = 4
user.save()

if save:
transaction.savepoint_commit(sp1)
else:
transaction.savepoint_rollback(sp1)

try:
with transaction.atomic():
user.create('limbo','illbehere@forever','mind blown',
'1111')
if not save: raise DatabaseError
except DatabaseError:
pass

Aquí podemos ver que después nos ocupamos de nuestros puntos de retorno, estamos utilizando el gestor de contexto transaction.atomic para revestir nuestra creación del usuario ‘limbo’. Cuando ese gestor de contexto se llama, es en efecto la creación de un punto de almacenamiento (porque ya estamos en una transacción) y que será punto de salvaguarda confirmar o retrotraer al salir del gestor de contexto.

Así, los siguientes dos pruebas describir su comportamiento:

def test_savepoint_rollbacks(self):

self.save_points(False)

#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)

#savepoint was rolled back so we should have original values
self.assertEquals(users[0].stripe_id, '')
self.assertEquals(users[0].name, 'jj')

#this save point was rolled back because of DatabaseError
limbo = User.objects.filter(email="illbehere@forever")
self.assertEquals(len(limbo),0)

def test_savepoint_commit(self):
self.save_points(True)

#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)

#savepoint was committed
self.assertEquals(users[0].stripe_id, '4')
self.assertEquals(users[0].name, 'starting down the rabbit hole')

#save point was committed by exiting the context_manager without an exception
limbo = User.objects.filter(email="illbehere@forever")
self.assertEquals(len(limbo),1)

Así que en realidad se puede utilizar ya sea atómica o punto de almacenamiento para crear puntos de salvaguarda dentro de una transacción. Con atómica usted no tiene que preocuparse explícitamente sobre el commit / rollback, donde como punto de almacenamiento con usted tiene control total sobre cuando esto sucede.

Conclusión

Si tuviera ninguna experiencia previa con versiones anteriores de las transacciones de Django, se puede ver cómo mucho más simple del modelo de transacción es. También tener confirmación automática por defecto es un gran ejemplo de incumplimientos “sano” que Django y Python tanto se enorgullecen de entrega. Para muchos sistemas que no tendrá que tratar directamente con las transacciones, acaba de dejar AUTOCOMMIT haga su trabajo. Pero si lo hace, es de esperar este post te habrá dado la información que necesita para gestionar las transacciones en Django como un profesional.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *