Errorhandling mehrer DB-Querys in rekursiver Funktion

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
schrodi
User
Beiträge: 16
Registriert: Dienstag 10. Mai 2016, 12:59

Hallo zusammen,

mein Name ist Andre. Euer Forum hat mir schon sehr oft mit Topics anderer User weitergeholfen, jetzt habe ich aber eine Frage, dessen Antwort ich bisher noch nicht gefunden habe.

Ich nutze Python 2.7 mit Django 1.9

Ich bin gerade dabei auf Basis von Jinja2 ein templatebasierten ConfigurationGui für Router und andere Netzwerkkomponenten zu bauen.
Mittels Jinja MetaAPI und JinjaSchema analysiere ich die Templates und möchte nun die notwendigen Datenbankeinträge vornehmen. Funktioniert auch soweit alles gut.

Folgende Models habe ich dafür erstellt:

Code: Alles auswählen

class Config(BaseModel):

	# Foreign Keys
	customer = models.ForeignKey('customer.Customer', related_name="customer_Customers_related", on_delete = models.CASCADE)
	site = models.ForeignKey('customer.Site', related_name="customer_sites_related", on_delete = models.CASCADE)
	product = models.ForeignKey('product.Product', related_name="product_products_related", on_delete = models.CASCADE)
	hardware = models.ForeignKey('product.Hardware', related_name="product_hardwares_related", on_delete = models.CASCADE)
	code_tree = TreeForeignKey('Code', null=True, related_name="confgen_codes_related", on_delete = models.CASCADE)

	# Info
	hostname = models.CharField(max_length=100, blank=False, null=False, unique=True)
	description = models.TextField(default="", blank=True)

	def __unicode__(self):
		return self.hostname

###########################################################################################################

# code tree 

class Code(MPTTModel):
	# Foreign Keys
	config = models.ForeignKey('Config', null=True, related_name="confgen_configs_related", on_delete = models.CASCADE)
	template = models.ForeignKey('Template', null=True, related_name="confgen_templates_related", on_delete = models.CASCADE)
	include_name = models.CharField(max_length=100, blank=True, null=False)

	# MPTT Field
	parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
	
	# Info
	name = models.CharField(max_length=50, blank=False, null=False)
	code = models.TextField(default='', null=False, blank=True)
	active = models.BooleanField(default=False)
	customized = models.BooleanField(default=False)	

	def __unicode__(self):
		return self.name

###########################################################################################################

# variable list
# possible values for select and choice fields

class VarValue(BaseModel):
	# Foreign Keys
	config = models.ForeignKey('Config', null=False, blank=False, related_name="confgen_config_related", on_delete = models.CASCADE)
	code = models.ForeignKey('Code', null=False, blank=False, related_name="confgen_code_related", on_delete = models.CASCADE)
	var = models.ForeignKey('Var', null=False, blank=False, related_name="confgen_var_related", on_delete = models.CASCADE)

	# Info
	#index = models.CharField(max_length=50, blank=False, null=False)
	value = models.CharField(max_length=512, blank=False, null=False)
	#var_type = models.CharField(max_length=10, blank=False, null=True)

	def __unicode__(self):
		return self.value

##########################################################################################################

# to config saved variables

class Var(BaseModel):
	# Foreign Keys
		
	#Info
	index = models.CharField(max_length=50, blank=False, null=False)
	values = models.CharField(max_length=1024, blank=False, null=True)
	var_type = models.CharField(max_length=10, blank=False, null=False)

	def __unicode__(self):
		return self.index

##########################################################################################################

class VarType(BaseModel):

	name = models.CharField(max_length=50, blank=False, null=False)
	format_regex = models.CharField(max_length=1024, blank=False, null=True)
	example = models.CharField(max_length=512, blank=False, null=False)

###########################################################################################################

class Template(BaseModel):
	# Foreign Keys
	product = models.ManyToManyField('product.Product',related_name='template_product')
	hardware = models.ManyToManyField('product.Hardware',related_name='template_hardware')
	folder = TreeForeignKey('Folder', null=False, blank=False, related_name="%(app_label)s_%(class)s_related", on_delete = models.CASCADE)

	#Info
	name = models.CharField(max_length=50, blank=False, null=False, unique=True)
	description = models.TextField()
	is_mastertemplate = models.BooleanField(default=False)
	path = models.CharField(max_length=1024, blank=False, null=False)
	code = models.TextField()

	def __unicode__(self):
		return self.name


Ich nutze folgende rekursive Funktion, um den MPTT Code zu befüllen. Der Tree enthält quasi als root-Eintrag das Mastertemplate (hier ist die Config-Logik enthalten) und die Config-Snippets als MPTT-children.

_templateTree ist ein nested Ordered Dict, das zuvor schon durch die Jinja include-Anweisungen erzeugt wird.
index = ein TemplateClass Object
value = OrderedDict mit den includierten Templates/Snippets

Code: Alles auswählen

def create_code_tree(self, configObj, parentObj=None, tree=None ):
		if tree is None:
			tree = self._templateTree

		for tmpl, nested_tmpls in tree.items():
			print tmpl.get_ID()

			# save to CodeTree
			codeObj = Code.objects.create(
					name=tmpl.get_ModelInstance().name,
					config=configObj, 
					template=tmpl.get_ModelInstance(), 
					include_name=tmpl.get_IncludeName(),
					code=tmpl.get_Code(),
				)
			if parentObj is not None:
				codeObj.parent = parentObj
			codeObj.save()

			# save to VarValue
			for var in tmpl.get_VarList():
				varObj = Var.objects.get(index=var)

				VarValue.objects.create(
					config=configObj,
					code=codeObj,
					var=varObj,
					)

			if nested_tmpls is not None:
				self.create_code_tree(configObj, codeObj, nested_tmpls)
Das Auslesen der Templates, das Speichern in der Datenbank funktioniert alles.
Da die Templates aber auch teilweise manuell ertellt werden, möchte ich ein ErrorHandling hinzufügen.

z.B. wenn eine im Template angelegte Variable nicht in der Tabelle Var entahlten ist, soll die Config erst gar nicht angelegt werden, sondern stattdessen mit einem Templateerror enden, den ich im Endeffekt als Fehler dem User anzeigen kann.

Bis es aber dazu kommt, dass der Befehl

Code: Alles auswählen

varObj = Var.objects.get(index=var)
eine DoesNotExist Exception wirft, sind bereits einige Zeilen in die Datenbank geschrieben.
Wie bereite ich quasi das Query vor, um zu prüfen ob ein Fehler eintritt, ohne bereits alles in DB zu schreiben?

Ich hoffe ich habe mich verständlich ausgedrückt.


Gruß Schrodi
BlackJack

@schrodi: Schau Dir mal Transaktionen an.
schrodi
User
Beiträge: 16
Registriert: Dienstag 10. Mai 2016, 12:59

Ok.

Habe mir jetzt mal das Thema Transaction vorgenommen. Aber so ganz erschließt sich mir das noch nicht, gerade in Bezug auf die rekusrive Funktion.

Nach meinem Verständnis müsste ich nur den Funktionsaufruf via Transaction durchführen ?

Code: Alles auswählen

if (form.is_valid()):
			#save config for mastertemplate
			new_config = form.save()
			template_id = request.POST.__getitem__("template_1")
			eng = engine.TemplateEngine(template_id)
			
			with transaction.atomic()
				eng.create_code_tree(new_config)
Das erscheint mir aber etwas zu einfach. Muss ich noch irgendwo Savepoints oder Rollbacks setzen?

Gruß Schrodi
BlackJack

@schrodi: Sofern die Ausnahme diesen Block abbricht wenn während der rekursiven Aufrufe das `DoesNotExist` auftritt, sollte es das grundsätzlich gewesen sein. Die Ausnahme möchtest Du dann eventuell an der Stelle noch behandeln und dem Benutzer das Problem dann irgendwie sinnvoll anzeigen.

Warum ``POST.__getitem__(…)``? Die ”magischen” Methoden sollte man nur Aufrufen wenn es gar nicht anders geht. Die Syntax für Index bzw. Schlüsselzugriff wäre ja sinnfrei wenn man stattdessen immer die dazugehörige magische Methode aufrufen würde.

Die Klammern um die ``if``-Bedingung sind überflüssig.
schrodi
User
Beiträge: 16
Registriert: Dienstag 10. Mai 2016, 12:59

Hi,

danke dir schon mal.

Leider funktioniert es noch nicht wie gewünscht. Habe jetzt folgendes gemacht:

Code: Alles auswählen

if form.is_valid():
			#save config for mastertemplate
			with transaction.atomic():
				try:
					new_config = form.save()
					template_id = request.POST.get("template_1")
					eng = engine.TemplateEngine(template_id)
					eng.create_code_tree(new_config)
				
					#return HttpResponseRedirect(reverse("confgen_config_globals_vrfs", args=[new_config.id, ]))
			
				except (IntegrityError, ObjectDoesNotExist), e:
					print e
					error.append(e)
Das Fehlerhandling funktioniert und wirft mir auch ein DoesNotExist Fehler aus, den ich auf der Seite angezeigt bekomme. Leider werden bis zu dem Fehler immer noch die DB-Einträge vorgenommen.

Ich habe jetzt eine Variable ins Template geschrieben, die nicht in der Referenztabelle existiert. Dieser Fehler wird in der 2. Rekursion der Funktion create_code_tree auch gecatch und die Abhandlung bricht an der Stelle ab. Alles was bisher aber an saves und creates durchgeführt wurde, verbleibt in der DB.


Gruß Schrodi
BlackJack

@schrodi: Der ``with``-Block wird ja auch nicht durch eine Ausnahme verlassen, also wird auch kein Rollback durchgeführt. Du hast das falsch herum verschachtelt. In der Django-Dokumentation ist doch ein Beispiel wie man es richtig macht.
schrodi
User
Beiträge: 16
Registriert: Dienstag 10. Mai 2016, 12:59

@BlackJack

Stimmt!
So funktioniert es.
Besten Dank!

Code: Alles auswählen

if form.is_valid():
			#save config for mastertemplate
			
			try:
				with transaction.atomic():
					new_config = form.save()
					template_id = request.POST.get("template_1")
					eng = engine.TemplateEngine(template_id)
					eng.create_code_tree(new_config)
				
					#return HttpResponseRedirect(reverse("confgen_config_globals_vrfs", args=[new_config.id, ]))
			
			except (IntegrityError, ObjectDoesNotExist), e:
				print e
				error.append(e)
Gruß
Antworten