Cześć,
jak pisałem we wcześniejszym wpisie, różne zdarzenia życiowe spowodowały że nagle mam znacznie więcej czasu, więc dziś poopowiadam Wam o ciekawym libie Python’owym – manhole. Z angielskiego manhole znaczy.. Właz. Kanalizacyjny. Taki jak mijacie na ulicach. A co on robi w Pythonie? Ano zgodnie z tytułem wpisu – pozwala wejść do programu w sposób interaktywny trochę tak „z boku”.
Ale powoli, napiszmy kawałek kodu który będzie agregował jakieś dane w pamięci. Niech ten program w odpowiedzi na wysyłanie do niego komunikatów wykonuje operacje które znajdują się w tym komunikacie. Uprośćmy to maksymalnie – komunikaty będą kazały wykonywać jedną z podstawowych działań matematycznych z aktualną wartością w pamięci a wartością wysłaną w owym komunikacie. Brzmi zagmatwanie? Nieee, jest turbo proste.
Stwórzmy 3 pliki w takiej hierarchii:
data_store
__init__.py
subscriber
__init__.py
main.py
I plik w folderze data_store niech ma taką zawartość:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import inspect myself = lambda: inspect.stack()[1][3] class DataStore(): def __init__(self): self.current_value = 0 self.history = [] def __add__(self, other): self.current_value = self.current_value + other self.history.append([ other, str(myself()) ]) def __mul__(self, other): self.current_value = self.current_value + other self.history.append([ other, str(myself()) ]) def __sub__(self, other): self.current_value = self.current_value - other self.history.append([ other, str(myself()) ]) def __div__(self, other): self.current_value = self.current_value / other self.history.append([ other, str(myself()) ]) def __str__(self): return str(self.current_value) data_store = DataStore() |
Nic skomplikowanego – klasa definiuje zachowania operatorów dodawania, odejmowania, mnożenia i dzielenia jako operacje na jednej z zmiennych tej klasy i zapisuje te zmiany jako historię.
Teraz plik w folderze subscriber:
1 2 3 4 5 6 7 8 9 | import redis def main(worker): redis_conn = redis.Redis() subscriber = redis_conn.pubsub() subscriber.subscribe('test') for item in subscriber.listen(): worker(item['data']) |
Co tu robimy? Prawie nic. Na każdą wiadomość która przyjdzie na kanał redisowy o nazwe „test” reagujemy wykonując przekazaną w parametrze funkcję. Banalne, nie?
I na koniec plik main.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from subscriber import main from datastore import data_store import operator from json import loads def worker(data): try: data = loads(data) print data op_func = getattr(operator, data['operation']) op_func(data_store, data['value']) except: pass if name == 'main': main(worker) |
Jako funkcję wejściową wykonujemy funkcję ze subscriber’a i przekazując mu jako argument naszą funkcję. Jaki będzie efekt? Bardzo prosty:
Jeśli na kanał wrzucimy 3 razy coś takiego:
redis-cli publish test '{"operation": "add", "value":6}'
To funkcja „worker” pobierze metodę dodającą i wykona na aktualnej wartości przechowywanej w data storze tę metodę. Co przy pierwszym wysłaniu spowoduje dodanie do 0 liczby 6. Drugie – do liczby 6 kolejną 6. I trzecie – do liczby 12(sumy) – kolejną 6. Co powoduje przechowanie w pamięci liczby 18 i 3 elementowej listy z historią tych operacji.
I teraz, na białym koniu, cały na biało – wjeżdża manhole.
Bo co jeśli w kodzie jest np. błąd i wartość przechowywana w pamięci jest zepsuta? Oczywiście można logować każdą zmianę i w myśl event source’ingu – odtworzyć stan i tak go skorygować dodatkowymi komunikatami by był poprawny, ale raz że to dość pracochłonne zajęcie a dwa wymusza wyczyszczenie pamięci programu co może wiązać się różnymi konsekwencjami. Np. straceniem tych danych które są poprawne lub przerwę w działaniu usługi.
Zmodyfikujmy więc nasz kod troszkę:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from subscriber import main from datastore import data_store import operator from json import loads import manhole def worker(data): if data == 'enable_manhole': manhole.install(locals={'data_store':data_store}) else: try: data = loads(data) print data op_func = getattr(operator, data['operation']) op_func(data_store, data['value']) except: pass if __name__ == '__main__': main(worker) |
Dodaliśmy tu obsługę „zainstalowania” naszego włazu programistycznego. Wiec sprawdźmy to! Wyślijmy:
redis-cli publish test 'enable_manhole'
co spowoduje wypisanie na standardowe wyjście naszego programu:
Manhole[17744:1524079049.5979]: Patched <built-in function fork> and <built-in function fork>.
Manhole[17744:1524079049.5988]: Manhole UDS path: /tmp/manhole-17744
Manhole[17744:1524079049.5988]: Waiting for new connection (in pid:17744) ...
Co znaczy że manhole się zainstalował w osobnym wątku i udostępnia dostęp do programu (a dokładniej do zmiennej data_store co jest zdefiniowane parametrem locals) jako socket unixowy pod ścieżką /tmp/manhole-17744.
Połączmy się!
sudo nc -U /tmp/manhole-17744
Co spowoduje info o przyjęciu połączenia:
Manhole[17744:1524079333.6725]: Started ManholeConnectionThread thread. Checking credentials …
Manhole[17744:1524079333.6726]: Accepted connection on fd:5 from PID:18093 UID:0 GID:0
a my mamy dostęp do programu w formie interaktywnej konsoli Pythona:
możemy też edytować zmienne:
Wydaje mi się że łatwo zauważyć potencjał tej biblioteki.
Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More
Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More
Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More
Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More
Pokaż komentarze
Ciekawy artykuł. Zdaje się, że koniecznie muszę nadrobić swoje zaległości jeśli chodzi o redisa, bo kiedy w przeszłości miałem okazję używać tej bazy, to nigdy nie bawiłem się w niej kanałami, a wyglądają one na zdecydowanie przydatną funkcjonalność. ;)