Objekte und deren Beziehungen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ABecker
User
Beiträge: 1
Registriert: Mittwoch 14. Dezember 2016, 09:53

Hallo Python - Community,

ich habe ein Problem damit Objekte und deren Beziehung zu einander heraus zu finden.

Ich habe folgende Situation. Ich habe zwei xml-Dateien. In einer XML-Datei stehen die Objekte und in der anderen XML-Datei sind die Beziehungen zwischen den Objekten enthalten. Die Objekte stehen in einer Baumstruktur zu einander in Beziehung. Die Dateien enthalten Objekte und entsprechend ihre Beziehungen aus mehreren Baumstrukturen. Ich suche nach einer eleganten Lösung durch die Angabe des obersten Objektes alle in der Baumstruktur enthaltenden Objekte und Beziehungen heraus zu finden. Bisher bin ich wie folgt vorgegangen. Ich habe alle Objekte aus den jeweiligen XML-Dateien in einer separaten Liste gespeichert. Die Objekte enthalten viele Attribute, nur leider gibt es keine Attribute in welchen der Beziehungsstatus unter den Objekten festgehalten ist. Ich habe versucht mit einer rekursiven Funktion beide Listen abzuarbeiten, aber das erscheint mir nicht so sinnvoll.

Ich müsste doch einfach nur die Objekte mit den Beziehungsinformationen anreichern, aber dazu fehlen mir wohl die Programmierfähigkeiten. Ich hoffe, dass mir hier geholfen werden kann.

Ich stelle mal den Code bereit von dem was ich im Moment habe.

Das Programm besteht aus drei Klassen:

eSIM_m.py

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable=R0903

import sys
from bmc_object_class import get_all_bmc_objs
from bmc_relations_class import get_all_bmc_objs_relations, findObject

l = len(sys.argv) - 1
usage = '\nUsage: some text'

if l == 0:
    print(usage)
else:
    sim_model = ''
    sim_model_mc_udid = ''
    target_cell = 'cell_zero'

    for i in range(l):
        if '-m' in sys.argv[i + 1]:
            sim_model = sys.argv[i + 2]
        if 'c' in sys.argv[i + 1]:
            target_cell = sys.argv[i + 2]

    all_bmc_objs_list = get_all_bmc_objs()
    all_bmc_objs_relations_list = get_all_bmc_objs_relations()
    try:
        sim_model_mc_udid = [
            i.mc_udid for i in all_bmc_objs_list if (i.Name == sim_model and i.__class__.__name__ == "BMC_BusinessService")].pop()
    except IndexError as e:
        pass
bmc_object_class.py

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable=R0903

import importlib
import os
import xml.etree.ElementTree as etree


class BMC_Base:
    _consumerList = []
    _providerList = []
    all_causes = None
    any_event_max_sev = None
    any_open_event_max_sev = None
    APPLICATION_event_max_sev = None
    AutoDeleteDelay = None
    business_data = None
    Category = None
    change_number = None
    comment = None
    Company = None
    component_scope = None
    ComponentAliases = None
    computed_priority = None
    computed_status = None
    consolidate_function = None
    consumer_num = None
    cost = None
    DATABASE_event_max_sev = None
    DatasetId = None
    Department = None
    Description = None
    DeviceID = None
    direct_events_count = None
    Floor = None
    highest_pn_event_score = None
    highest_pn_predicted_severity = None
    HomeCell = None
    HomePageURI = None
    impact_cost = None
    impact_priority = None
    impact_sla_rollup_status = None
    impact_status = None
    ImpactCostPerSec = None
    ImpactCostPerSecOut = None
    ImpactCostUnit = None
    impacting_open_event_max_sev = None
    InstanceId = None
    Item = None
    last_status_modification = None
    maintenance_mode = None
    manual_status = None
    manual_status_comment = None
    manual_status_providers = None
    manual_status_providers_count = None
    manual_status_requestor = None
    ManufacturerName = None
    mc_associations = None
    mc_bad_slot_names = None
    mc_bad_slot_values = None
    mc_creation_time = None
    mc_modification_request_time = None
    mc_modification_requestor = None
    mc_modification_time = None
    mc_udid = None
    Model = None
    Name = None
    NETWORK_event_max_sev = None
    Notes = None
    OTHER_event_max_sev = None
    OwnerContact = None
    OwnerName = None
    pn_predict_to_occur_time = None
    PNReadAcl = None
    PNWriteAcl = None
    possible_causes = None
    Priority = None
    PriorityOut = None
    PriorityWatchdog = None
    publish_env_id = None
    publish_providers_cis = None
    raw_impact_priority = None
    ReadSecurity = None
    Region = None
    Room = None
    root_causes = None
    schedule_status = None
    ScheduleId = None
    self_priority = None
    self_status = None
    SelfPriorityFunction = None
    SelfPriorityFunctionParam = None
    SerialNumber = None
    shadow_cells = None
    ShortDescription = None
    Site = None
    SiteGroup = None
    sla_rollup_status = None
    source = None
    status = None
    StatusModel = None
    sub_status = None
    SYSTEM_event_max_sev = None
    TokenId = None
    Type = None
    USER_TRANSACTIONS_event_max_sev = None
    UsersAffected = None
    UsingOrganization = None
    UsingOrganizationId = None
    VersionNumber = None
    WriteSecurity = None

    def __str__(self):
        return "mc_udid: %s -> Name: %s -> ComponentAliases: %s" % (self.mc_udid, self.Name, self.ComponentAliases)


class BMC_BusinessService(BMC_Base):
    ServiceType = None


class BMC_Application(BMC_Base):
    ApplicationType = None
    isVirtual = None


class BMC_ComputerSystem(BMC_Base):
    CapabilityList = None
    Domain = None
    HostName = None
    isVirtual = None
    PrimaryCapability = None
    SystemType = None
    VirtualSystemType = None


class BMC_System(BMC_Base):
    isVirtual = None


class BMC_SystemComponent(BMC_Base):
    SystemClassId = None
    SystemName = None


class BMC_SystemService(BMC_Base):
    SystemClassId = None
    SystemName = None


def convert_propag_xml2py(xml_entries, py_entries, obj_type):
    for entry in xml_entries:
        new_obj = obj_type()
        for attr in entry:
            setattr(new_obj,
                    attr.tag.strip(),
                    attr.text)
            new_obj.xml_element = entry
        py_entries.append(new_obj)


def get_all_bmc_objs(xml_filename='./var/data/MC_SM_COMPONENT.xml',
                     update_data=True):
    all_bmc_objs = []
    if update_data:
        os.system("command")

    class_list = ['BMC_Application',
                  'BMC_BusinessService',
                  'BMC_ComputerSystem',
                  'BMC_System',
                  'BMC_SystemComponent',
                  'BMC_SystemService']

    TREE = etree.parse(xml_filename)
    ROOT = TREE.getroot()

    for class_name in class_list:
        class_obj = getattr(importlib.import_module(
            "bmc_object_class"), class_name)
        MAP_ENTRIES = ROOT.findall('./IMPACT_DATA/%s' % (class_name))

        convert_propag_xml2py(MAP_ENTRIES, all_bmc_objs, class_obj)
    return all_bmc_objs
bmc_relations_class.py

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable=R0903

import os
import xml.etree.ElementTree as etree
from bmc_object_class import BMC_Base


class BMC_Impact(BMC_Base):
    consumer_home_cell = None
    consumer_id = None
    PreventProviderAutoDelete = None
    propagated_status = None
    propagated_sub_status = None
    PropagationModel = None
    provider_classname = None
    provider_home_cell = None
    provider_id = None
    State = None
    StatusWeight = None
    true_impact = None

    def __str__(self):
        return "mc_udid: %s -> consumer_id: %s -> provider_id: %s" % (self.mc_udid, self.consumer_id, self.provider_id)


def convert_propag_xml2py(xml_entries, py_entries, obj_type):
    for entry in xml_entries:
        new_obj = obj_type()
        for attr in entry:
            setattr(new_obj,
                    attr.tag.strip(),
                    attr.text)
            new_obj.xml_element = entry
        py_entries.append(new_obj)


def get_all_bmc_objs_relations(xml_filename='./var/data/MC_SM_RELATIONSHIP.xml',
                               update_data=True):
    all_bmc_objs_relations = []
    if update_data:
        os.system("command")

    TREE = etree.parse(xml_filename)
    ROOT = TREE.getroot()
    MAP_ENTRIES = ROOT.findall('./IMPACT_DATA/BMC_Impact')

    convert_propag_xml2py(MAP_ENTRIES, all_bmc_objs_relations, BMC_Impact)
    return all_bmc_objs_relations


def findObject(all_bmc_objs_list, mc_udid):
    for obj in all_bmc_objs_list:
        if obj.mc_udid == mc_udid:
            return obj
Wie in dem Code zu sehen ist, speichert eine Klasse die Objekte und die andere Klasse die Beziehungen. Am besten wäre es glaube ich wenn ich am Ende die Objekte direkt abfragen könnte, welche Beziehung sie haben. Nur stehe ich auf dem Schlauch. Ich habe nicht leisesten Plan ich wie da ran gehen soll.

MfG
ABecker
Zuletzt geändert von Anonymous am Mittwoch 14. Dezember 2016, 12:34, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Abecker: Das sind nicht drei Klassen sondern drei Module, mit deutlich mehr Klassen. Beziehungsweise ``class``-Anweisungen bei denen ich mir nicht sicher bin ob das tatsächlich alles Klassen sind, weil keine davon wirklich sinnvoll Methoden definiert. Das würde einem die global in jeder Datei deaktivierte Pylint-Meldung auch sagen.

`_consumerList` und `_providerList` werden nicht verwendet und sollten auch ganz bestimmt nicht auf der *Klasse* definiert werden. Wie auch die ganzen anderen Attribute nicht. Das ist IMHO ein schmutziger Trick diese dort, und nicht in einer `__init__()` zu definieren. Die Frage ist aber ob man die dort überhaupt und die ganzen Klassen so braucht, oder ob man nicht *eine* Klasse schreiben kann, die beim Erstellen die erlaubten Attribute übergeben bekommt und eine `__getattr__()`-Methode besitzt, die nachschaut ob es ein vorgesehenes Attribut ist und dann `None` zurück gibt. Dann kann man sich wahrscheinlich diese ganzen anderen Klassen sparen solange da nicht tatsächlich auch spezifische Methoden verknüpft werden.

Grunddatentypen gehören nicht in Namen. Die ändert man zu schnell im Laufe der Programmentwicklung mal und dann hat man entweder falsche, irreführende Namen, oder man muss im ganzen Programm die betroffenen Namen anpassen.

`convert_propag_xml2py()` und `convert_propag_xml2py()` heissen nicht nur gleich, sondern machen auch genau das selbe. Die Funktion sollte also nur *einmal* im Programm existieren. Das das `py_entries`-Argument verändert wird ist unschön, weil sehr überraschend. Die Funktion sollte die neu erstellen Objekte als Rückgabewert liefern.

Das dort sämtliche Attribute der XML-Elemente als Attribute auf dem Ergebnisobjekt gesetzt werden, kann gefährlich sein. Ich würde das in ein Wörterbuch umsetzen und das in der jeweiligen Klasse kapseln.

`get_all_bmc_objs_relations()` und `get_all_bmc_objs()` heissen fast gleich, und machen fast das gleiche. Diese Redundanz sollte man auch beseitigen.

Die Verarbeitung der Kommandozeilenargumente ist kaputt. So wie das da steht darf weder im auf der Kommandozeile angegebenen `sim_model` noch in der `target_cell`-Angabe '-m' oder 'c' vorkommen. Sonst wird das auch als Schalter erkannt und es passiert Murks. Zur Verarbeitung von Kommandozeilenargumenten gibt es in der Standardbibliothek das `argparse`-Modul. Das kann auch gleich Typen umwandeln und eine Hilfe-Ausgabe erzeugen wenn der Benutzer nichts oder ``--help`` oder ``-h`` übergibt.

Zum algorithmischen Problem: Erstelle ein Wörterbuch das die Werte über die die Objekte für die Verbindungen identifiziert werden auf die Objekte selbst abbildet. Wenn das zwei verschiedene Attribute für Quelle und Ziel einer Verbindung sind, deren Wertemengen sich überschneiden können, braucht man zwei solcher Abbildungen. Und wenn man dann die Verbindungen verarbeitet, kann man sich die beteiligten Enden aus diesen Wörterbüchern anhand der ID holen. Und an das oder die Objekte hängen. Wie genau man das macht, hängt davon ab wie die Kardinalitäten aussehen und wie im weiteren Programmverlauf die Zugriffsmuster aussehen. Will man den Graphen über die Knoten in beide Richtungen traversieren können? Sind das 1:1 oder 1:N oder N:M Beziehungen? Worüber sollen die Kanten selektiert werden können?

Ich glaube ich würde die XML-Elemente/Attribute von den Knoteninformationen im Programm sauber trennen wollen. Also dass die Attribute aus der XML-Datei nicht direkt auf den `BMC_*`-Klassen stehen sondern dort über ein Attribut zur Verfügung stehen. So dass man nicht direkt über `obj.Room` an einen Wert kommt, sondern über `obj.attr.Room`. Dann bleibt der Namensraum von `obj` frei für eigene Attribute und Methoden die man im Programm braucht, und es besteht nicht die Gefahr das Attribute aus dem XML-Dokument mit welchen im Programm kollidieren.

Mir ist da auch ein bisschen zu viel Magie drin, wie das suchen von Objekten anhand ihres Klassennamens. Das würde ich in einem Attribut kodieren statt den Klassennamen zu ermitteln, oder dadurch das alle `BMC_BusinessService`-Objekte in einer Datenstruktur gespeichert werden.

Das mit der `class_list` die gar keine Klassen sondern Klassennamen enthält und die Klassen dann dynamisch aus einem Modul importiert, ist auch ein bisschen sehr magisch und unnötig kompliziert. An der Stelle hätte man gleich eine Liste mit den Klassen anlegen können. Und dann dort im XML nach den Klassennamen suchen können. Allerdings, wie schon gesagt, finde ich diese starre Zuordnung unschön. Ein Wörterbuch das XML-Tagnamen auf Klassen abbildet, wäre flexibler und würde einen auf Python-Seite auch nicht die Klassennamen aufzwingen, deren Unterstrich beispielsweise den Namenskonventionen widerspricht und deren Präfix eigentlich überflüssig ist, weil der in Python schon vom Modulnamen abgedeckt wird.
Antworten