30 mar 2017

Podsumowanie pierwszego miesiąca DSP i plany dalszego działania

Kończy się marzec, a wraz z nim pierwszy miesiąc trwania konkursu Daj Się Poznać. Portfel Emerytalny, czyli mój projekt konkursowy, naturalnie również jest rozwijany już miesiąc. Co przez ten czas udało się zrobić, a gdzie zawaliłem sprawę? Jakie mam plany na kwiecień i jakie zmiany czekają bloga? O tym wszystkim w dzisiejszym wpisie.

27 mar 2017

Połączenie backendu z frontendem, czyli odbieranie danych z API w Angular

W dzisiejszym poście zamierzam pokazać jak odebrać dane z API w Angular i wyświetlić je w aplikacji - czyli ciąg dalszy spinania aplikacji w całość. Na razie wyświetlę tylko suche dane, bez "ubierania" ich w wykres - na ten temat pojawi się osobny post.

23 mar 2017

Python Flask i MySQL - łatwo i szybko czy droga przez mękę?

Kontynuując post z poniedziałku pozostaję w temacie połączenia Flask i MySQL. W dzisiejszym wpisie będzie case study jak to połączenie odbyło się w moim projekcie - w końcu pora zacząć spinać wszystkie elementy w jedną, działającą całość.

W poprzednim wpisie podałem kilka modułów do MySQL dostępnych dla Flask, ale nie wspomniałem którego użyję. Pierwsza wersja backendu używała Flask-MySQL, zdecydowałem jednak o zmianie na Flask-MySQLdb. Różnica niewielka, ale przekonała mnie kompatybilność z Python 3+. Niestety po wydaniu polecenia
pip install flask-mysqldb
zobaczyłem jedynie błąd
running build_ext
building '_mysql' extension
error: Microsoft Visual C++ 9.0 is required. Get it from http://aka.ms/vcpython27
pobranie Microsoft Visual C++ Compiler for Python 2.7 i uruchomienie instalacji zmieniło tylko komunikat na
_mysql.c(29) : fatal error C1083: Cannot open include file: 'my_config.h': No such file or directory
Na szczęście jest stackoverflow, zgodnie ze znalezioną poradą pobrałem odpowiednią paczkę ze strony http://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient (w moim przypadku mysqlclient-1.3.10-cp27-cp27m-win32.whl), kilka poleceń w konsoli
pip install wheel
Successfully installed wheel-0.29.0

pip install .\mysqlclient-1.3.10-cp27-cp27m-win32.whl
Successfully installed mysqlclient-1.3.10

pip install flask-mysqldb
Installing collected packages: flask-mysqldb
Successfully installed flask-mysqldb-0.2.0
Zatem w końcu sukces, a to dopiero początek drogi.

Jak wczytać ustawienia z zewnętrznego pliku
Niezależnie jednak od użytego modułu pojawiła się potrzeba zapisania danych połączenia z bazą w osobnym pliku. Można to zrobić na wiele sposobów, np.
  • plik z kodem Python, który importuje się jak inne moduły
  • JSON
  • YAML, czyli specjalny format do przechowywania konfiguracji
  • plik INI
  • standardowy XML
Bardzo dobre zestawienie tych metod razem z przykładami użycia jest dostępne tutaj.
Najłatwiejsza i wystarczająca w moim przypadku wydaje się metoda nr 1 i tak powstał plik databaseconfig.py:
mysql = {'host': 'database_host',
         'user': 'database_user',
         'passwd': 'database_passwd',
         'db': 'database_name'}
Kod!
Pora na programistyczne mięcho. Mam już wszystko żeby napisać kolejną metodę do API,  postanowiłem więc przygotować dane do wyświetlenia na wykresie w interfejsie użytkownika.
Dodaję import flask_mysqldb oraz jsonify z modułu flask (tworzy obiekt response z jsonem który można zwrócić bezpośrednio z metody), wczytuję konfigurację z pliku. Nagłówek pliku app.py z wygląda teraz tak:
from flask import Flask
from flask import Response
from flask import jsonify
from flask_mysqldb import MySQL

app = Flask(__name__)

import databaseconfig as cfg
app.config['MYSQL_USER'] = cfg.mysql['user']
app.config['MYSQL_PASSWORD'] = cfg.mysql['passwd']
app.config['MYSQL_DB'] = cfg.mysql['db']
app.config['MYSQL_HOST'] = cfg.mysql['host']
mysql = MySQL(app)
a metoda pobierająca dane z bazy tak:
@app.route("/dbtest")
def fetchfromdb():
    query_string = "SELECT Date, Value FROM rate WHERE Date > '2015-01-01' AND FundId = 1" 
    cursor = mysql.connection.cursor()
    cursor.execute(query_string)
    return jsonify(data=cursor.fetchall())
Sukces? Jeszcze nie. Niestety przygotowana przeze mnie metoda po wywołaniu wyrzuciła błąd z parsowaniem typu decimal
TypeError: Decimal('110.11') is not JSON serializable
Parsowanie decimal
O co chodzi z tym błędem? Otóż JSON w swojej specyfikacji nie uwzględnia takiego typu jak decimal, więc autorzy Flask nie implementowali swojej wersji tego jak ma być serializowany. Chcesz mieć decimal - musisz sam zdecydować jak ma on być zapisywany. Można oczywiście pozbyć się decimali z zapytania do bazy
SELECT Date, CAST(Value * 100 AS INT) FROM rate WHERE Date > '2015-01-01' AND FundId = 1
ale to tylko obejście problemu zamiast jego rozwiązania.
Na szczęście domyślny parser JSON można nadpisać swoją implementacją, w której zdefiniujemy zachowanie dla wybranych typów, a reszta zostanie w domyślnej postaci:
import flask.json
import decimal

class MyJSONEncoder(flask.json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, decimal.Decimal):
            # Convert decimal instances to strings.
            return str(obj)
        return super(MyJSONEncoder, self).default(obj)
app.json_encoder = MyJSONEncoder
Po takiej zmianie aplikacja działa już poprawnie i zwraca dane jakie możemy odebrać we frontendzie.

Po rozwiązaniu powyższego problemu dowiedziałem się jeszcze o bibliotece simplejson, która zawiera domyślną metodę obsługi decimal. Chciałem jednak pozostać przy Flask.jsonify że względu na to, że zwraca od razu obiekt Response gotowy do zwrócenia z funkcji. Nie testowałem, ale ponoć
If you install the simplejson package, flask.jsonify will automatically use that instead of the stdlib json library.
Na koniec odniosę się do pytania, które umieściłem w tytule. Niestety nie mogę powiedzieć żeby połączenie z MySQL było wybitnie łatwe - w całym procesie pojawiło się kilka problemów. Część z nich wynika na pewno z mojej nieznajomości Pythona. Na szczeście nie była to również droga przez mękę i nadal uważam że Flask był dobrym wyborem jeśli chodzi o szybkie prototypowanie backendu. Zobaczymy czy i jak długo ta opinia się utrzyma :)

20 mar 2017

Przegląd dostępnych rozwiązań do połączenia Flask i MySQL

Ten post jest pierwszą częścią opisu współpracy Flask i MySQL. Kiedy zacząłem tworzyć wpis dotyczący połączenia bazy i backendu w moim projekcie, to chciałem w nim opisać kompleksowo wszystko czego się nauczyłem w trakcie poznawania Flask. Post rozrósł się do ogromnego wypracowania, dlatego został podzielony na części. Dzisiaj opiszę dostępne metody takiego połączenia, na pewno pojawi się również opis konkretnego wdrożenia na przykładzie Portfela Emerytalnego. Być może na tym się nie skończy, bo temat jest naprawdę obszerny.

Poszukiwania metody połączenia się z bazą przyniosły odkrycie, że istnieje kilka modułów których możemy użyć w tym celu. Znalezione przeze mnie to:
Wymieniłem tylko takie związane wprost z Flask, bo dla samego Pythona istnieje ich wiele więcej (MySQLdb, mysqlclient, Connector/Python, SQLObject, peewee, PonyORM itd.). Taki wybór w założeniu ma zapewnić brak problemów w użyciu w połączeniu z Flask.
Krótki opis każdego z modułów (forma jest celowo dość ścisła, post ma być szybką ściągawką dla mnie i być innych szukających tego typu zestawienia).

Flask-MySQL

Strona projektu: https://flask-mysql.readthedocs.io
Chyba najprostszy z wymienionych modułów, oparty na wymienionym powyżej MySQLdb. Niestety z tego powodu działa tylko z Python w wersji 2.4-2.7. Dla mnie nie jest to problem, ale jeśli ktoś pisze w Python 3+ to nie skorzysta z tego rozszerzenia. Ciągle dość popularny i dobrze wypozycjonowany, ja jednak nie będę go używał.

Inicjalizacja:
from flask import Flask
from flaskext.mysql import MySQL

app = Flask(__name__)
mysql = MySQL()
app.config['MYSQL_DATABASE_HOST'] = 'localhost'
app.config['MYSQL_DATABASE_USER'] = 'db_username'
app.config['MYSQL_DATABASE_PASSWORD'] = 'db_password'
app.config['MYSQL_DATABASE_DB'] = 'db_name'
mysql.init_app(app)
Użycie:
connection = mysql.connect()
cursor = connection.cursor()
cursor.execute("SELECT * FROM user")
data = cursor.fetchall()

Flask-MySQLdb

Strona projektu: http://flask-mysqldb.readthedocs.io
W skrócie i uproszczeniu: ulepszona wersja poprzedniego modułu, obsługuje Python 3. Zacytuję wypowiedź autora którą znalazłem:
I wrote it because back then flask-mysql didn't work outside of a request context (not sure if it does now) and because it's based on MySQL-python while flask-mysqldb is based on mysqlclient which is fully backwards compatible fork of MySQL-python and is compatible with Python 3.3+. You mentioned you're using Python 3.4.3 so flask-mysql will not work.
flask-mysqldb is really meant to be a tiny, tiny wrapper for mysqlclient so if you need something more like an ORM flask-sqlalchemy is definitely the way to go.
Inicjalizacja:
from flask import Flask
from flask_mysqldb import MySQL

app = Flask(__name__)
app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'db_username'
app.config['MYSQL_PASSWORD'] = 'db_password'
app.config['MYSQL_DB'] = 'db_name'
mysql = MySQL(app)
Użycie:
cursor = mysql.connection.cursor()
cursor.execute("SELECT * FROM user")
data = cursor.fetchall()

Flask-SQLAlchemy

Strona projektu: http://flask-sqlalchemy.pocoo.org/2.1/
Bardziej ORM niż prosty moduł do połączenia z bazą. Najpopularniejszy wśród Pythonowych ORMów, prawdopodobnie dlatego dorobił się dedykowanego modułu dla Flask.

Inicjalizacja:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://username:password@localhost/db_name'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    email = db.Column(db.String(120), unique=True)

    def __init__(self, username, email):
        self.username = username
        self.email = email

    def __repr__(self):
        return '' % self.username
Użycie:
data = User.query.all()

16 mar 2017

Baza danych: struktura, przykładowe dane i połączenie

W ostatnim poście napisałem, że kolejnym krokiem będzie odbieranie we frontendzie przykładowych danych z api. Życie jednak zweryfikowało te plany i trafiłem na jakiś błąd parsowania json przez przeglądarkę w momencie odbierania żądania. Nie mogłem się go pozbyć pomimo wielu prób, stwierdziłem zatem, że szkoda na to czasu. Lepiej sprawdzić czy ten błąd występuje również kiedy odbiorę dane z bazy i te dane zserializuję do json. Jeśli będzie ten sam błąd - to wtedy poszukam co jest nie tak, a jest szansa że problem w ogóle się nie pojawi.W związku z powyższym w dzisiejszym poście opiszę przygotowanie bazy danych.

Swoją bazę stawiam w hekko.pl, mam u nich wykupiony hosting więc przy okazji użyję go do tego projektu. Samo wygenerowanie bazy odbywa się przez panel DirectAdmin i sprowadza się do kilku kliknięć, więc nie ma tu zbyt wiele do opisania. Jest dostępny phpMyAdmin do zarządzania bazą, ale mam trochę wstręt do niego i od razu pobrałem MySQL Workbench. Przy pierwszej próbie połączenia okazało się, że muszę jeszcze wprost ustawić jakie hosty mają dostęp do bazy. Na czas testów dodałem %, czyli po prostu dostęp dla wszystkich

Struktura bazy na potrzeby pierwszej wersji portfela emerytalnego prezentuje się następująco:
Na potrzeby wyświetlenia we frontendzie przykładowego wykresu potrzebuję jedynie wartości funduszy w czasie, więc zakładam tylko tabele Fund (będzie zawierała nazwy funduszy) oraz Rate (wartość w czasie):
CREATE TABLE `fund` (
 `Id` int(11) NOT NULL AUTO_INCREMENT,
 `Name` varchar(256) NOT NULL,
 PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `rate` (
 `Id` int(11) NOT NULL AUTO_INCREMENT,
 `Date` date NOT NULL,
 `Value` decimal(10,2) NOT NULL,
 `LastUpdate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
 `FundId` int(11) NOT NULL,
 PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Pozostało jeszcze tylko uzupełnić tabelę przykładowymi danymi. Kursy funduszu pobieram ze strony TFI i parsuję przy pomocy narzędzia regexr - wyrażenia regularne spisują się znakomicie do takiego wyselekcjonowania danych
Wszystko łączę w zapytania i wykonuję na bazie:
INSERT INTO fund (`Name`) VALUES ('NN (L) Stabilny Globalnej Alokacji (K)')
INSERT INTO rate (`Date`, `Value`, `FundId`) VALUES
('2014-09-24', 106.66, 1),
('2014-09-25', 106.44, 1),
('2014-09-26', 106.83, 1),
('2014-09-29', 106.71, 1),
...
To tyle na dziś, kolejny post będzie o tym jak te dane z bazy odebrać w backendzie i wystawić je jako json. Zapowiada się długi wpis, bo ponownie pojawiło się kilka problemów - po szczegóły zapraszam w poniedziałek.

13 mar 2017

Pierwsze kroki z Python, Flask i Heroku

W trakcie pisania portfela emerytalnego chcę nauczyć się czegoś nowego, stąd pomysł na użycie języka Python. Po krótkich poszukiwaniach znalazłem też prosty framework do API, czyli Flask. Uruchomienie przykładu ze strony Flask na localhost poszło jeszcze szybciej niż postawienie szkieletu strony w Angular i sprowadza się do utworzenia jednego pliku i wywołania dwóch komend w konsoli. Nie chcę przepisywać treści z oficjalnej strony więc zainteresowanych odsyłam na http://flask.pocoo.org/

Wybierając Python jako język w którym powstanie backend miałem nadzieję (a nawet byłem przekonany), że będę mógł uruchomić napisane przeze mnie skrypty na hostingu który posiadam. O jakże naiwne było to podejście ;) Nawet jeśli udałoby się uruchomić jakiś prosty skrypt to nie było mowy o zainstalowaniu czegokolwiek - a ja potrzebowałem wspomniany wyżej Flask.
Na szczęście są platformy w chmurze w modelu PaaS (Platform as a Service, czyli dostajemy gotowe całe środowisko w którym uruchamiamy swoje aplikacje). Nigdy nie korzystałem z takich platform, więc znowu pojawiła się okazja do nauki nowych rzeczy. Po szybkim przeszukaniu sieci znalazłem Google App Engine (GAE) oraz Heroku. W obydwu tych miejscach można bez problemu można uruchomić prostą aplikację bez konieczności płacenia, a kiedy potrzebujemy większych zasobów to płacimy za ich zużycie.

Moje pierwsze kroki skierowałem w stronę GAE, ale ilość opcji i skomplikowanie instalacji szybko mnie zniechęciły. Heroku okazało się nie mieć żadnej z tych wad i po krótkiej konfiguracji udało się utworzyć aplikację retirement-savings-api i uruchomić jej pierwszą wersję - standardowe Hello World o którym pisałem we wstępie.
Pewnym zaskoczeniem dla mnie było to, jak uploaduje się aplikację na serwer. Działa to tak, że po stronie serwera mamy uruchomione repozytorium git - więc wystarczy
heroku git:remote -a retirement-savings-api
git add .
git commit -m "stub"
git push heroku master
i aplikacja już jest na serwerze gotowa do działania. Jeszcze słowem wyjaśnienia - komenda heroku wywoływana powyżej pochodzi z narzędzia udostępnionego przez Heroku, ale nic nie stoi na przeszkodzie żeby użyć po prostu
git remote add heroku https://git.heroku.com/retirement-savings-api.git
Zachęcony początkowymi sukcesami postanowiłem przygotować jakieś konkretne dane z których będzie mógł skorzystać frontend. Na początek dodałem statyczny plik z jsonem który API ma odczytywać i po prostu zwracać do klienta. Dopisałem krótką metodę:
@app.route("/test")
def testdata():
    file = open('static/testdata.json', 'r') 
    file_contents = file.read()
    resp = Response(file_contents, status=200, mimetype='application/json')
    return resp
Ponownie commit, push i sprawdzam https://retirement-savings-api.herokuapp.com/test. Wszystko działa jak należy, dane z pliku wyświetlają się, więc teraz pora odebrać te dane po stronie frontu - ale to już materiał na następnego posta.

9 mar 2017

Jak postawić stronę w Angular 2 w 5 minut

Pora w końcu napisać bardziej techniczny post. Na pierwszy ogień idzie frontend. Tak naprawdę samego kodu nie będzie zbyt wiele, bo dziś opiszę jak postawić działający szkielet witryny i uruchomić go lokalnie. Tytuł nie jest przesadzony i naprawdę można to zrobić w 5 minut, co zaraz się okaże.

6 mar 2017

Emerytalny Portfel Inwestycyjny - opis projektu

Zaczynamy tydzień kolejnym wpisem, dziś przekazuję więcej informacji na temat projektu, który realizuję w ramach konkursu Daj Się Poznać. Czym więc jest tytułowy Emerytalny Portfel Inwestycyjny? Krótko mówiąc - miejscem gdzie można śledzić postępy w oszczędzaniu na emeryturę, zobaczyć na wykresie jak przyrastają (lub, o zgrozo - topnieją) nasze inwestycje, dostać powiadomienie jeśli warto zamienić dane aktywa na inne oraz sprawdzić kiedy w takim tempie na tę emeryturę uzbieramy.

1 mar 2017

Hello World

Witaj na moim blogu! W pierwszym wpisie wypada się przedstawić, zatem kilka słów o mnie: tata, mąż i programista .NET z ponad 7 letnim stażem. Moim hobby są finanse (w każdym aspekcie) a w wolnym czasie nałogowo oglądam seriale.