Hej, chciałem poruszyć kwestię walidacji plików konfiguracyjnych. W aplikacji nad którą teraz siedzę, mimo, że została zaprojektowana dopiero w 1/3, mój plik konfiguracyjny naprawdę się rozrósł. Na chwilę obecną korzystam z takiej fajnej opcji jak łączenie słowników - w kodzie przechowuję słownik ze wszystkimi kluczami jakie można wykorzystać w pliku konfiguracyjnym i domyślnymi wartościami. Wczytuję plik konfiguracyjny, tworzę z niego słownik i łączę go z domyślnym. Jednak potrzebuję czegoś jeszcze, do walidacji wartości kluczy.

Załóżmy, że mamy klucz foo, a jego wartość może być True lub False. Użytkownik wpisując w pliku konfiguracyjnym wartość 'bar' może zepsuć jakąś ładną funkcję. Cały plik konfiguracyjny musi być zweryfikowany na samym starcie aplikacji. Opisywanie każdej party klucz-wartość IFami nie za specjalnie mi podchodzi, bo jest to ogromna ilość par klucz-wartość. Regexy też nie są fajną opcją do tego.
Do tego dochodzą też takie cyrki jak np. klucz foo1 musi mieć ustawioną konkretną wartość jeśli klucz foo2 jest True (przykład: jeśli korzystamy z bazy danych musimy mieć podany IP i port).

Czy w ogóle jest sens walidować plik konfiguracyjny, a jeśli tak to dlaczego?
Znacie może jakieś narzędzia, najlepiej oparte na licencji MIT pod Pythona do walidacji? Dla podpowiedzi, korzystam z plików konfiguracyjnych w formacie YAML, do parsowania używam PyYaml.

zadane 15 Maj '11, 06:48

Retefumu's gravatar image

Żadnego ciekawego, gotowego narzędzia nie znalazłem, więc pomyślałem i naskrobałem własne.
Działa rekurencyjnie, więc nie straszne mu zagnieżdzone słowniki.

Najpier definiujemy przykładowy słownik:

test = {'foo' : 'bar',

        'fo1' : { 'ba1' : 'ba2'}

       }

W przypadku gdy chcemy dany klucz walidować, zamiast podawać parę key - value podajemy key - tuple(value, lambda). Wygląda to tak:

default = {'foo' : ('bar', lambda v: v == 'bar' or 'dwabary'),

        'fo1' : { 'ba1' : (lambda v: True if v == 'ba1' and default['foo'][0] == 'dwabary')}

       }

Funkcja walidująca wygląda tak:

def check_values(loaded_config, level = []):

    for item in loaded_config.iteritems():

        key, value = item[0], item[1]

        if value.__class__ == dict:

            check_values(value, level + [key])

        else:

            tmp_dict = self.default.copy()

            for l in level:

                tmp_dict = tmp_dict[l]

                dvalue = tmp_dict[key]

                if dvalue.__class__ == tuple and dvalue[1](dvalue[0]):

                    print True // Tutaj wpisujemy co ma zostać wykonane jeśli 

                               // wartość jest poprawna

                elif dvalue.__class__ != tuple:

                    print True // Tutaj to samo

                else: print False    

check_values(wczytany_słownik)

W kodzie musimy mieć self.default jako słownik z domyślnymi wartościami, w którym umieszczamy lambdę. Nie zostanie wyrzucony błąd jeśli wartość klucza nie będzie (value, lambda) - wtedy nie zostanie podjęta żadna akcja. Oczywiście nie ma problemu by rozwinąć tę funkcję, by przekazywać jej także domyślny słownik, ale nie mam takiej potrzeby :)

Wszelkie uwagi i/lub ulepszenia powyższej funkcji będą bardzo mile widziane w komentarzach :) Szczególnie w przypadku wywoływaniu słownika w lambda, brzydko to wygląda.

Nadal jednak jestem zainteresowany innymi metodami walidacji takiego słownika, tak więc nie zamykam jeszcze pytania.

EDIT:

Zadałem to samo pytanie na innym serwisie. Dla potomnych, szukających rozwiązania tego problemu, biblioteka: http://pypi.python.org/pypi/voluptuous robi to doskonale.

trwały link

odpowiedziane 15 Maj '11, 16:47

Retefumu's gravatar image

edytowane 15 Maj '11, 23:53

Hmmm moim skromnym zdaniem plik konfiguracyjny najczęściej jest zbiorem statycznych wartości, które sterują pracą całej aplikacji. Dostęp do tego pliku powinna mieć jedynie osoba upoważniona, mająca wiedzę niezbędną do jego modyfikowania (a zatem odpowiedzialna za poprawne wprowadzenie wartości).

Chętnie jednak wysłucham opinii innych gdyż zaintrygował mnie ten temat. Swoją drogą myślę również, że konfiguracja na bazie danych nie jest też złym pomysłem. Łatwa kontrola dostępu oraz częściowa kontrola typów przechowywanych danych.

Pozdrawiam

trwały link

odpowiedziane 15 Maj '11, 07:53

anonymous's gravatar image

Tak, dostęp do niego mają tylko upoważnione osoby. Jednak co, jeśli aplikacja działa online i jest wymagana do poprawnego działania całego serwisu, można przeładować jej konfigurację w czasie działania (wywołanie metody reload na klasie konfiguracyjnej), a osoba upoważniona popełni głupi błąd, dla przykładu zamiast wartości Null poda False która nie jest obsługiwana przez program? Walidacja składni jest przeprowadzana przez parser, więc o to jestem spokojny. Jednak walidacja semantyki już do roli parsera nie należy.

(15 Maj '11, 14:17) Retefumu

IMHO walidacje powinieneś wykonać a to kiedy to zrobisz (czy podczas parsowania pliku czy później podczas używania pary klucz-wartość) nie ma znaczenia.

Osobiście zrobiłbym to najpóźniej jak możliwe (tak jestem leniwy ;) czyli podczas bezpośredniego korzystania z par klucz-wartość bo jak sam mówisz:

Do tego dochodzą też takie cyrki jak np. klucz foo1 musi mieć ustawioną konkretną wartość jeśli klucz foo2 jest True (przykład: jeśli korzystamy z bazy danych musimy mieć podany IP i port).

To podczas parsowania pliku może wystąpić sytuacja gdy klucz foo2 zostanie zdefiniowany po foo1.

trwały link

odpowiedziane 15 Maj '11, 16:09

michalp's gravatar image
  1. Jeśli będziesz walidował tą samą wartość w wielu miejscach, nie dość, że łamiesz zasady DRY to także narażasz się na spory nadkład pracy jeśli nagle uznasz, że wartość może mieścić się nie tylko w [True, False] ale też i Null.

  2. Nie widzę problemu by najpierw wczytać cały plik, skonwertować go na słownik i wtedy dopiero walidować semantykę :)

(15 Maj '11, 16:32) Retefumu

Twoja odpowiedź

toggle preview

Podstawy składni Markdown

  • *kursywa* lub __kursywa__
  • **pogrubienie** lub __pogrubienie__
  • link:[tekst](http://url.com/ "Tytuł")
  • obraz?![alt tekst](/path/img.jpg "Tytuł")
  • wypunktowanie: 1. Foo 2. Bar
  • aby przełamać wiersz, po prostu dodaj dwie spacje w miejscu, w którym chcesz, aby nowy wiersz się rozpoczął
  • podstawowoe znaczniki HTML również są obsługiwane

Tagi pytania:

×151
×15

zadane: 15 Maj '11, 06:48

wyświetlane: 856 razy

ostatnia aktywność: 15 Maj '11, 23:53