Mała strona z Pythonem

Ostatnio dłubię sobie stronę do obsługi programu javowego MegaMek. Jest tutaj: https://github.com/seem8/astech. Oczywiście pierwszym problemem było: w jakim pythonowym frameworku to zrobić? Jest Django, jest Flask, jest Bottle, jest Cherrypy, jest Pyramid i jeszcze jakieś 20 działających.

Zauważyłem, że są generalnie dwie kategorie stron internetowych:

  • operujące na dużych ilościach danych,
  • robiące coś fajnego.

Najistotniejsze w pierwszej kategorii jest utrzymanie spójności danych i bardzo szybkie na nich operowanie. Mamy kolekcję rzeczy, albo sklep, albo terminarz, albo bardzo dużo plików, albo coś takiego. I wówczas lepiej sprawdzają się duże frameworki, które pozwalają na tym się właśnie skupić, upraszczające tworzenie formularzy, wyszukiwarek, itp…

W drugiej kategorii ważne jest pozostawienie programiście swobody i generalnie zostawienie go w spokoju. Zauważyłem, że bardzo to lubię. Dlaczego? Bo można po prostu programować w Pythonie i nie przejmować się w ogóle faktem, że będzie to obsługiwane przez przeglądarkę.

Przedstawiam małe demo, które obrazuje powyższe:

#!/usr/bin/env python3

def read_logs(log_file):

  try:
    open(log_file,'r').close()
  except FileNotFoundError:
    open(log_file,'w').close()
    
  with open(log_file,'r') as myfile:
    mylines = myfile.readlines()
    lastlog = mylines[len(mylines)-50 : len(mylines)]
    lastlog.reverse()

    return lastlog

Powiedzmy, że mamy coś takiego. Zwykły kod, który działa wszędzie. Skorzystajmy więc z tej funkcji w programie tekstowym:

#!/usr/bin/env python3

def read_logs(log_file):

  try:
    open(log_file,'r').close()
  except FileNotFoundError:
    open(log_file,'w').close()

  with open(log_file,'r') as myfile:
    mylines = myfile.readlines()
    lastlog = mylines[len(mylines)-50 : len(mylines)]
    lastlog.reverse()

    return lastlog


important_logs = read_logs('journal.txt')

for i in important_logs:
  print(i)

Znowu nic niezwykłego. Wczytałem fragment logów systemowych i wypisałem 50 ostatnich linijek, korzystając z funkcji read_logs. Teraz zróbmy tak samo, ale żeby logi pokazywały się na stronie interenetowej.

#!/usr/bin/env python3

from bottle import template, route, run
def read_logs(log_file):

  try:
    open(log_file,'r').close()
  except FileNotFoundError:
    open(log_file,'w').close()

  with open(log_file,'r') as myfile:
    mylines = myfile.readlines()
    lastlog = mylines[len(mylines)-50 : len(mylines)]
    lastlog.reverse()

    return lastlog

@route('/')
def index_page():
  important_logs = read_logs('journal.txt')
  return template('index_page', logs=important_logs)

run(host='localhost', port=8080)

Tyle Pythona. Bottle używa dekoratorów, żeby określić jaki rodzaj funkcji deklarujemy: get, post, route, czy jeszcze jakiś inny. Jako „parametr” dekoratora wpisujemy ścieżkę, którą wpiszemy do przeglądarki. W tym wypadku po wpisaniu w pasek adresu localhost:8080 pokażą się nasze logi. Template potrzebuje co najmniej jednej informacji: jak ma się nazywać plik z końcówką .tpl, w którym będzie kod html. Oprócz tego możemy przekazać dowolną ilość argumentów, które pozwolą szablonowi na dostęp do naszych zmiennych oraz funkcji. Zmienna jest dla szablonu, a wartość to coś z naszego kodu Pythona, którą może być wszystko: zmienna, funkcja z parametrami, egzemplarz klasy, czy cokolwiek sobie umyślimy.

Musi to być jeszcze zrozumiałe dla przeglądarki, więc mamy do tego, wspomniany w kodzie, osobny plik index_page.tpl:

<html>
<head>
<title>Important Logs</title>
<head>
<body>

% for i in logs:
<p>{{i}}</p>
% end

</body>
</html>

Jest tutaj odrobina magicznych rzeczy:

  • znakiem % rozpoczynamy linijkę zawierającą kod Pythona,
  • na zmiennych w kodzie html operujemy poprzez konstrukcję {{zmienna}},
  • jako, że html ignoruje białe znaki, a Python ich właśnie używa do „kończenia” instrukcji for/if/def/itd…, korzystamy ze słowa kluczowego end, żeby wyjść z pętli.

Ścieżką może to być ‚/’, ‚/login’, albo ‚/read_logs/<log_file>’, co pozwala na przekazywanie argumentów na poziomie hiperłączy. Mógłbym więc rozpisać funkcję index_page w ten sposób:

@route('/read_logs/<log_file>')
def index_page(log_file):
  important_logs = read_logs(log_file)
  return template('index_page', logs=important_logs)

Wówczas łącze do przeglądarki wyglądałoby tak: http://localhost:8080/read_logs/journal.txt. Oczywiście jeżeli pozostawiamy cokolwiek użytkownikowi, zwłaszcza w internecie, trzeba się odpowiednio zabezpieczyć, ale o ciasteczkach i logowaniu napiszę innym razem.

Jeżeli ktoś widział kiedyś kod Django, to zapewne czuje już różnicę. To jest bardzo, bardzo proste. Pozwala mi skupić się na logice programu, który czasem robi dziwne rzeczy i nie stawia mi na drodze plików konfiguracyjnych oraz mnóstwa modułów. W dużym programie prędzej czy później taka swoboda odbija się czkawką (i poczułem to na sobie), ale w małym programie, tak do 1000 wierszy, robi świetną robotę.

Ten wpis został opublikowany w kategorii #!/kody, stdout i oznaczony tagami , , . Dodaj zakładkę do bezpośredniego odnośnika.