Fedora OSError libgdal.so undefined symbol GEOSMakeValid_r

Fedora Logo

Kilka dni temu nagle przestał mi się uruchamiać lokalnie projekt w Pythonie, rzucając nie do końca przyjemnym błędem: OSError: /usr/gdal30/lib/libgdal.so.26: undefined symbol: GEOSMakeValid_r. Pierwsze – z jakiegoś dziwnego powodu – co przyszło mi do głowy, to aktualizacja paczek Pythona. Ale nawet odtworzenie czystego venv-a nie rozwiązało problemu. Czytaj dalej „Fedora OSError libgdal.so undefined symbol GEOSMakeValid_r”

Zmiana rozdzielczości działania Xvfb na Travis CI

O ile uruchamianie „zwykłych” testów w ramach ciągłej integracji (Jenkins, Travis CI, Circle CI) raczej nie jest problematyczne – bo przecież uruchamiamy je tylko przeciwko konkretnym fragmentom kodu – to testy wymagające Xvfb (X virtual framebuffer) już takie oczywiste nie są.

Po zainstalowaniu Xvfb na Travisie powinien być dostępny plik konfiguracyjny tej usługi – /etc/systemd/system/xvfb.service. Jego zmiana  wymagać będzie sudo.
Warto sprawdzić, jaka rozdzielczość ustawiona jest domyślnie – u mnie było to 1024x768px – co w sumie w czasach obecnych bardziej pokrywa się z urządzeniami mobilnymi i błędnie założyłem, że Travis będzie operował na wyższej rozdzielczości – no bo przecież mamy XXI wiek.
W samych testach, mimo wymuszania maksymalizacji okna otwartej przeglądarki – niektóre testy kończyły się niepowodzeniem. Okazało się, że problemem jest właśnie rozdzielczość ekranu, a nawet zmaksymalizowane okno było na tyle małe, że strona wyświetlana była jak na urządzeniach mobilnych, przez co pewne elementy strony nie były widoczne w ogóle albo wymagały wykonania dodatkowych akcji. Wyjaśnia to też, dlaczego lokalnie testy przechodziły bez zająknięcia.

Niemało stresu kosztowało mnie znalezienie sposobu na wymuszenie, aby xvfb uruchamiał okienka w większej rozdzielczości – gdy już udało się to rozgryźć – okazuje się to wyjątkowo banalne.

Jedyne co trzeba było wykonać, to zatrzymać usługę xvfb, poleceniem – na przykład sed – zmienić poprzednio ustawioną rozdzielczość na nową w pliku konfiguracyjnym usługi i ponownie uruchomić usługę, czyli:


before_install:
# …
-sudo service xvfb stop
-sudo sed -i 's/1024×768/1280×1024/g' /etc/systemd/system/xvfb.service
-sudo service xvfb start
# …

view raw

.travis.yml

hosted with ❤ by GitHub

Dodatkowo, Travis CI pozwala na zbieranie artefaktów ale  ograniczeni jesteśmy tylko do wysyłania tego do AWS, a dokładniej do S3 (przynajmniej w chwili obecnej) – debugowanie było z tego powodu mniej przyjemne. Robot Framework, w przypadku niepowodzenia robi zrzuty ekranu – rozdzielczość tych zrzutów ekranu możemy uzyskać poleceniem file selenium-screenshot-1.png – oczywiście, wymagać to będzie uruchomienia buildu w trybie debugowania i/lub dostępu do utworzonych wcześniej artefaktów.

Przy okazji kilka kolejnych wskazówek, które mogą się przydać w pracy z Travisem.

  1. Podczas uruchamiania buildu w trybie debugowania otrzymujemy polecenie, którego potrzebujemy, aby zalogować się do Travisa. Warto dodać parametr -o ConnectTimeout=0 do tego polecenia SSH, jak w poniższym przypadku:
    ssh -o ConnectTimeout=0 unique-id-xyz@to2.tmate.io
    Oczywiście, potrzebne to jest tylko wtedy, gdy nie mamy tego ustawionego na stałe w konfiguracji SSH.
    Po zalogowaniu będziemy mieć możliwość wykonywać polecenia ręcznie – analogicznie do automatycznego wykonywania kolejnych poleceń z pliku .travis.yml.
  2. Gdy już będziemy zalogowani – aby przyspieszyć pracę i wykonanie poszczegółnych grup poleceń które wiemy, że nie stanowią problemu, możemy korzystać z poleceń basha: travis_run_* – czyli travis_run_before_install, travis_run_install, travis_run_before_script, itd – do każdej sekcji z naszego pliku .travis.yml dodajemy po prostu prefix travis_run_ i powinny wykonać się wszystkie polecenia z tej sekcji.
  3. Ponieważ Travis w momencie napotkania błędu, przy włączonej – chyba nawet domyślnie – opcji fast_finish: true oraz błędów w linii poleceń – zamyka połączenie SSH – sugeruję, by w trakcie debugowania ustawić w Bashu set +e, zabezpieczy nas to przed przedwczesnym zakończeniem builda z wynikiem błędu (jeśli wpiszemy coś nieprawidłowo w linii poleceń) i będziemy mogli na spokojnie przyjrzeć się komunikatom zwróconym przez wywoływane programy.
  4. Próby ustawiania różnych (wyższych) rozdzielczości dawały dziwne rezultaty:
    1280×1024 px -> 1050×888 px
    1920×1080 px -> 945×944 px
    1680×1050 px -> 825×914 px
    czyli jak widać, największą szerokość zrzutu ekranu dała pierwsza rozdzielczość.
  5. Nie ma możliwości komunikacji z Travisem poprzez scp – więc próba przesłania artefaktów w ten sposób także się nie powiedzie (uwzględniając kwestie przekazywania agenta SSH).

Cały przykład można znaleźć na GitHub i bezpośrednio na Travisie, a przykładowy plik .travis.yml poniżej:


dist: xenial
notifications:
email: false
sudo: required
language: python
python:
– "3.7"
cache:
pip: true
addons:
apt:
packages:
– xvfb
chrome: stable
# https://firefox-source-docs.mozilla.org/testing/geckodriver/geckodriver/Support.html
firefox: "57.0"
services:
– xvfb
before_install:
– set +e
– sudo service xvfb stop
– sudo sed -i 's/1024×768/1280×1024/g' /etc/systemd/system/xvfb.service
– sudo service xvfb start
install:
– pip install -r requirements.txt
before_script:
– python mysite/manage.py migrate
# moved hire due to time to run
– python mysite/manage.py runserver &
# Keep in mind Xenial Chrome Stable version and Chromedriver version
– wget http://chromedriver.storage.googleapis.com/74.0.3729.6/chromedriver_linux64.zip
– unzip chromedriver_linux64.zip
– sudo mv chromedriver /usr/local/bin
– sudo chmod a+x /usr/local/bin/chromedriver
# Install Firefox (Gecko) driver
– wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz
– tar -xvzf geckodriver-v0.24.0-linux64.tar.gz
– sudo mv geckodriver /usr/local/bin
– sudo chmod a+x /usr/local/bin/geckodriver
script:
– robot -d ./results tests/
after_script:
– file results/selenium-screenshot-1.png

view raw

.travis.yml

hosted with ❤ by GitHub

PyCharm – jedna konfiguracja na wielu instancjach

Ponownie chciałbym pochwalić PyCharma – w sumie to wszystkie IDE od JetBrains.

Otóż narzędzie to pozwala nam na współdzielenie naszej ulubionej konfiguracji środowiska pomiędzy różnymi instancjami – jeśli używamy tego IDE w pracy i w domu to warto z tego skorzystać.
Oczywiście ma to też ten plus, że w przypadku kradzieży/zniszczenia możemy w całkiem prosty sposób odzyskać poprzednią konfigurację.

A sprawa jest całkiem prosta:

  1. Zakładamy nowe repozytorium, np. w serwisie GitHub (tak, może być też GitLab albo jakiekolwiek inne – ja lubię GitHub)
  2. W PyCharm wybieramy kolejno File > Settings Repository i ustawiamy konkretny Upstream URL na adres repozytorium
  3. Nadpisujemy zdalne (Overwrite Remote)
  4. To samo robimy na pozostałych instancjach nadpisując ustawienia lokalne (Overwrite Local)

Gdy już mamy zsynchronizowane, każda zmiana powinna być automatycznie pobierana z i wysyłana do repozytorium – jednak opcja Merge też w niektórych sytuacjach może się przydać 🙂

Wąż w stringach

Jednym z pierwszych typów danych o których uczymy się w ramach najróżniejszych kursów programowania jest typ łańcuchowy – często określany jako string. Nie bez powodu jest to jeden z najczęściej używanych w programowaniu typów. A dlaczego? Ponieważ jako ludzie głównie działamy na tekście: czytamy książki, piszemy listy, ostrzegamy o wysokim napięciu, opisujemy substancje chemiczne, gryzmolimy tagi na rozkładach jazdy (kto wracał komunikacją nocną i nie mógł odczytać rozkładu przez głupie tagi wie, że tego typu teksty są najmniej lubiane) – w dużym skrócie – tekstu używamy wszędzie.

Oczywiście ma to swoje konsekwencje -w programowaniu wykorzystujemy tekst aby wyświetlić cokolwiek na ekranie, do logowania aktualnego stanu aplikacji, gdy prosimy użytkownika o podanie informacji, przesyłamy informacje przez sieć.

Łańcuch znaków jest po prostu kolekcją… znaków. Mogą to być zarówno znaki drukowalne, jak np. litery alfabetu, cyfry czy znaki interpunkcyjne lub niedrukowalne – znaki o specjalnym znaczeniu – spacje, tabulacje, „dzwonek systemowy”.
Stringi mogą mieć długość od zera do… prawie rozmiaru pamięci zainstalowanej w komputerze. Oczywiście, mają tutaj jeszcze ogromne znaczenie inne czynniki – np. wersja Pythona czy architektura komputera, jednak dla codziennych zastosowań nie powinniśmy się tym aż tak bardzo przejmować. Gdybyśmy jednak chcieli zrobić kopię zapasową Internetu możemy w pewnym momencie zderzyć się z błędem MemoryError.

Długość (między innymi) łańcuchów określa się wbudowaną w Pythona funkcją len jednak należy mieć na uwadze, że ilość znaków, którą widzimy to nie zawsze rzeczywista długość łańcucha trzymana w pamięci. Posiłkując się StackOverflow:


>>> test = u'ë́aúlt' # znaki można policzyć na palcach jednej ręki (dla regionów oddalonych od Czarnobyla)
>>> len(test)
6
>>> # Hmm, chyba jednak jesteśmy blisko uszkodzonego reaktora…
>>> for c in test:
… print("{} – {}".format(c, len(c)))
ë – 1
– 1
a – 1
ú – 1
l – 1
t – 1
>>> # i wszystko "jasne"…

view raw

strlen_py3.py

hosted with ❤ by GitHub

Dla Pythona 2 wyniki są jeszcze bardziej zaskakujące (w odpowiedzi na StackOverflow został właśnie użyty Python 2).

Stringi w Pythonie możemy zapisywać w kodzie na kilka różnych sposobów:


>>> print("Witaj świecie")
Witaj świecie
>>> print("Witaj 'świecie'")
Witaj 'świecie'
>>> print("Witaj \"świecie\"")
Witaj "świecie"
>>> print('Witaj świecie')
Witaj świecie
>>> print('Witaj "świecie"')
Witaj "świecie"
>>> print("""Witaj 
świecie""")
Witaj
świecie
>>> print('''Witaj 
świecie''')
Witaj
świecie
>>> print("Witaj\t\t\t\n\t\t\t\tświecie")
Witaj
świecie
>>> print('Witaj\t\t\t\n\t\t\t\tświecie')
Witaj
świecie

view raw

pystrings.py

hosted with ❤ by GitHub

Jednak wyświetlanie na ekranie prostych łańcuchów znaków może w pewnym momencie stać się dość nudne – na szczęście wynaleziono interpolację.

Interpolacja to wg Słownika Języka Polskiego: „wstawienie wyrazów, zwrotów, zdań do pierwotnego tekstu; też: takie wstawione wyrazy, zwroty, zdania”.
W naszej sytuacji chodzi o podstawienie wartości ze zmiennych w konkretne miejsca w łańcuchu znaków.
Przykładowo jeśli posiadamy szablon z informacją o uczniu i jego ocenie: "Cześć {imię i nazwisko ucznia}. Uzyskałeś {ilość punktów} punktów z egzaminu." to każde nasze wystąpienie {coś tam} będziemy mogli podmienić konkretnymi wartościami.
Do uzyskania konkretnego łańcucha znaków – z już podmienionymi wartościami możemy podejść (ponownie) na kilka sposobów – lepszych lub gorszych.
I tak na przykład, jednym z nich – niezbyt fajnym ani wygodnym – będzie po prostu łączenie łańcuchów znaków:


uczen = "Alpha Bravo"
punkty = 80
print("Cześć " + uczen + ". Uzyskałeś " + str(punkty) + " punktów z egzaminu.")

Nie da się chyba tego lubić. Przy okazji warto zwrócić uwagę, że pamiętać musimy o rzutowaniu na odpowiedni typ funkcją str albo zderzymy się z błędem TypeError.

Inny ciekawy (dla masochistów) sposób, to zapisanie wszystkiego jako lista i jej połączenie pustym łańcuchem:


uczen = "Alpha Bravo"
punkty = 80
print("".join(["Cześć ", uczen, ". Uzyskałeś ", str(punkty), " punktów z egzaminu."]))

Kod tak wspaniały, że aż oczy bolą… No dobra, są sytuacje, gdzie podobne podejście będzie akceptowalne, całkiem zwięzłe i czytelne… ale to nie jest ten przypadek.

Zmierzamy powoli w kierunku lepszego jutra – rozwiązanie dość popularne jeszcze do niedawna:


uczen = "Alpha Bravo"
punkty = 80
print("Cześć %s. Uzyskałeś %d punktów z egzaminu." % (uczen, punkty))

Plusem tego sposobu interpolowania łańcucha jest to, że korzysta on z wielu dostępnych specyfikatorów formatowania – co pozwala nam z kolei przyoszczędzić na jawnym konwertowaniu wartości zmiennych na łańcuchowy typ danych – musimy jednak zadbać o odpowiedni specyfikator w stringu.

Pierwszy z zalecanych (moim zdaniem) sposobów interpolowania to wykorzystanie metody format obiektu string:


uczen = "Alpha Bravo"
punkty = 80
print("Cześć {}. Uzyskałeś {} punktów z egzaminu.".format(uczen, punkty))

Sposób ten jest tak fajny, że aż dorobił się własnej strony internetowej z najróżniejszymi przykładami.

Drugim zalecanym (znów, moim zdaniem) sposobem jest użycie tzw f-stringów – dostępnych niestety dopiero od Pythona 3.6:


uczen = "Alpha Bravo"
punkty = 80
print(f"Cześć {uczen}. Uzyskałeś {punkty} punktów z egzaminu.")
print(f"2 + 2 = { 2 + 2 }")

Jeszcze innym sposobem interpolowania o którym warto wspomnieć jest użycie klasy Template z modułu string:


from string import Template
uczen = 'Alpha Bravo'
punkty = 80
tpl = Template("Cześć $uczen. Uzyskałeś $punkty punktów z egzaminu.")
print(tpl.substitute(uczen=uczen, punkty=punkty))

jednak nie spotykałem się z nim zbyt często – warto jednak wiedzieć, że coś takiego jest.

Więcej o samej interpolacji w Pythonie poczytać możemy w oficjalnych dokumentach PEP-0215PEP-0498, PEP-0501 oraz PEP-0502.

Dodatkowo, sam łańcuch znaków jako obiekt ma całkiem sporo metod pozwalających operować na danym łańcuchu, przytaczając kilka takich metod:


# https://pl.wikipedia.org/wiki/Pangram
pangram = "pChNąĆ w Tę łÓdŹ jEżA lUb OśM sKrZyŃ fIg"
print(pangram.capitalize())
print(pangram.casefold())
print(pangram.title())
print(pangram.upper())

UWAGA: Wywołanie takiej metody na stringu nie powoduje jej zmiany w pamięci.

I tym oto sposobem przechodzimy do faktu, że łańcuchy znaków są niezmiennym typem danych (tzw. immutable). Cool. I co to znaczy?
W dużym skrócie chodzi o to, że jeśli zmienimy łańcuch znaków to zmieni się jego lokalizacja w pamięci – zostanie utworzony nowy, zmodyfikowany obiekt. Najlepiej zaobserwujemy to na przykładzie:


>>> my_str_i = "Alpha"
>>> my_str_ii = "Bravo"
>>> my_str_iii = my_str_i
>>> my_str_iv = "Alpha"
>>> hex(id(my_str_i))
'0x7f5fec555960'
>>> hex(id(my_str_ii)) # 2 różne wartości w różnych miejscach pamięci
'0x7f5fe67e71f0'
>>> hex(id(my_str_iii)) # po przypisaniu referencji do obiektu obie zmienne wskazują to samo miejsce w pamięci
'0x7f5fec555960'
>>> hex(id(my_str_iv)) # ta sama wartość może być trzymana w pamięci pod jednym adresem
'0x7f5fec555960'
>>> my_str_i += ' Delta' # zmieniamy referencję ponieważ tworzymy nowy obiekt i wrzucamy go w inne miejsce pamięci
>>> hex(id(my_str_i))
'0x7f5fe684e730'
>>> hex(id(my_str_iii)) # pozostałe referencje nie zostały zmienione
'0x7f5fec555960'
>>> hex(id(my_str_iv))
'0x7f5fec555960'

Dla wygody skorzystałem z wbudowanych funkcji hex i id.

Na koniec warto wspomnieć o dostarczanym ze standardową biblioteką Pythona modułem string, który głównie może okazać się przydatny jeśli będziemy potrzebowali kolekcji znaków: małych lub dużych liter, cyfr (także ósemkowych lub szesnastkowych) czy białych znaków, itp.

Dodatkowe źródła:
[1] A string of unexpected lengths
[2] Moduł string

Narzędzia do statycznej analizy kodu źródłowego w PyCharm

Kilkukrotnie wspominałem już o IDE PyCharm i jego możliwościach. Oprócz kolorowania składni i obsługi kilku innych głupotek pozwala na przykład definiować narzędzia, które później możemy uruchamiać w trakcie pisania kodu.

Jako programiści Pythona możemy do tych narzędzi zaliczyć te dbające o jakość pisanego przez nas kodu. Oprócz standardowego PEP8 – który jest tylko czubkiem góry lodowej – osobiście chciałbym polecić także: flake8, pylint oraz radon.

flake8 jako linter sprawdza kod pod kątem zgodności ze standardami – dodatkowo rozszerza wspomniany przed chwilą PEP8 o kilka innych wartościowych usprawnień, np. takie związane z komentarzami, nazewnictwem zmiennych, itp.

Podobnie ma się sytuacja z pylint-em, jednak jego zaletą jest ocena naszego kodu z użyciem skali – każda zmiana może wpływać pozytywnie lub negatywnie zgodnie z następującym wzorem. Satysfakcją jest uzyskać ocenę 10.00/10.00 :). Oprócz oceny liczbowej otrzymujemy także zestawienie i najróżniejsze statystyki.

radon natomiast w całkiem przyjemny sposób prezentuje nam informacje na temat złożoności fragmentów naszego kodu – może nam się wtedy zapalić czerwona lampka, że pewne elementy powstałego kodu mogą potencjalnie stanowić wąskie gardło w aplikacji i są dobrym miejscem zaczepienia jeśli chodzi o refaktoryzację.

Zainstalujmy więc sobie narzędzia w czystym środowisku wirtualnym:


pushd ~/Envs
~/bin/Python-3.6.4/bin/python3 -m venv pytools
source pytools/bin/activate
pip install pylint
pip install radon

flake8 zostanie zainstalowany jako zależność podczas instalacji radon-a.

Klikamy „+” aby dodać nowe narzędzie. W otwartym okienku uzupełniamy jak poniżej:

Oczywiście dobrym pomysłem jest także grupowanie naszych narzędzi, np. ze względu na technologię oraz konfiguracja skrótów klawiaturowych:

Poniżej kilka zrzutów po uruchomieniu:

  • flake8:


~/Envs/pytools/bin/flake8 setup.py
setup.py:5:1: F401 're' imported but unused
setup.py:15:1: E302 expected 2 blank lines, found 1
setup.py:38:1: E305 expected 2 blank lines after class or function definition, found 1
setup.py:52:80: E501 line too long (134 > 79 characters)
setup.py:100:80: E501 line too long (116 > 79 characters)
Process finished with exit code 1

  • pylint


~/Envs/pytools/bin/pylint setup.py
Using config file ~/.pylintrc
************* Module setup
C: 52, 0: Line too long (134/100) (line-too-long)
C:100, 0: Line too long (116/100) (line-too-long)
W: 8, 0: Redefining built-in 'open' (redefined-builtin)
C: 1, 0: Missing module docstring (missing-docstring)
C: 13, 0: Constant name "here" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
C: 15, 0: Missing class docstring (missing-docstring)
E: 32, 8: Unable to import 'pytest' (import-error)
W: 22,12: Attribute 'pytest_args' defined outside __init__ (attribute-defined-outside-init)
W: 24,12: Attribute 'pytest_args' defined outside __init__ (attribute-defined-outside-init)
W: 29, 8: Attribute 'test_suite' defined outside __init__ (attribute-defined-outside-init)
C: 43, 0: Constant name "packages" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
C: 45, 0: Constant name "requires" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
C: 52, 0: Constant name "test_requirements" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
C: 54, 0: Constant name "about" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
W: 56, 4: Use of exec (exec-used)
C: 59, 4: Constant name "readme" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
C: 61, 4: Constant name "history" doesn't conform to '(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern (invalid-name)
W: 5, 0: Unused import re (unused-import)
Report
======
39 statements analysed.
Statistics by type
——————
+———+——-+———–+———–+————+———+
|type |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module |1 |NC |NC |0.00 |0.00 |
+———+——-+———–+———–+————+———+
|class |1 |NC |NC |0.00 |0.00 |
+———+——-+———–+———–+————+———+
|method |3 |NC |NC |100.00 |0.00 |
+———+——-+———–+———–+————+———+
|function |0 |NC |NC |0 |0 |
+———+——-+———–+———–+————+———+
Raw metrics
———–
+———-+——-+——+———+———–+
|type |number |% |previous |difference |
+==========+=======+======+=========+===========+
|code |63 |60.58 |NC |NC |
+———-+——-+——+———+———–+
|docstring |21 |20.19 |NC |NC |
+———-+——-+——+———+———–+
|comment |3 |2.88 |NC |NC |
+———-+——-+——+———+———–+
|empty |17 |16.35 |NC |NC |
+———-+——-+——+———+———–+
Duplication
———–
+————————-+——+———+———–+
| |now |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines |0 |NC |NC |
+————————-+——+———+———–+
|percent duplicated lines |0.000 |NC |NC |
+————————-+——+———+———–+
Messages by category
——————–
+———–+——-+———+———–+
|type |number |previous |difference |
+===========+=======+=========+===========+
|convention |11 |NC |NC |
+———–+——-+———+———–+
|refactor |0 |NC |NC |
+———–+——-+———+———–+
|warning |6 |NC |NC |
+———–+——-+———+———–+
|error |1 |NC |NC |
+———–+——-+———+———–+
Messages
——–
+——————————-+————+
|message id |occurrences |
+===============================+============+
|invalid-name |7 |
+——————————-+————+
|attribute-defined-outside-init |3 |
+——————————-+————+
|missing-docstring |2 |
+——————————-+————+
|line-too-long |2 |
+——————————-+————+
|unused-import |1 |
+——————————-+————+
|redefined-builtin |1 |
+——————————-+————+
|import-error |1 |
+——————————-+————+
|exec-used |1 |
+——————————-+————+
———————————–
Your code has been rated at 4.36/10
Process finished with exit code 22

  • radon


~/Envs/pytools/bin/radon cc setup.py
setup.py
M 18:4 PyTest.initialize_options – A
C 15:0 PyTest – A
M 26:4 PyTest.finalize_options – A
M 31:4 PyTest.run_tests – A
Process finished with exit code 0

view raw

radon.result.sh

hosted with ❤ by GitHub

Więcej doczytać można w następujących źródłach:

  1. About style guide of python and linter tool. pep8, pyflakes, flake8, haking, Pylint.
  2. What is Flake8 and why we should use it?

Sortowanie po wirtualnej kolumnie w Django

Nie tak dawno temu w ramach jednego z zadań w Django było uporządkowanie wyników wg innego klucza niż najprostsze ORDER BY. Chodziło o nadanie pewnego rodzaju wag dla wierszy, które możliwie najlepiej wpasowały się w poniższe kryteria:

  • pierwszeństwo miały wiersze, które zawierały dokładne dopasowanie szukanej frazy
  • następnie wiersze, które od szukanej frazy się zaczynały
  • kolejno wiersze, które na podaną frazę się kończyły
  • kończąc wierszami, które podaną frazę zawierały w dowolnym miejscu
  • opcjonalnie pozostałe wiersze, jeśli query nie było jedynym kryterium filtrowania.

Przydatny okazał się poniższy kawałek kodu tworzący nieistniejącą w modelu CaseWhen columnę exact w postaci nowych warunków w obiekcie QuerySet  – po której następnie sortował:


queryset = queryset.annotate(
exact=Case(
When(
name__iexact=query,
then=Value('1')
),
When(
name__istartswith=query,
then=Value('2')
),
When(
name__iendswith=query,
then=Value('3')
),
When(
name__icontains=query,
then=Value('4')
),
default=Value('5'),
output_field=CharField(),
)
).order_by('exact', 'name')

Dodatkowe porządkowanie wg kolumny name służy do alfabetycznego uporządkowania wierszy, którym została nadana ta sama waga.

Jeśli nie zostanie przekazana fraza do szukania (parametr q) używana jest fraza beer. Żeby mieć pogląd jakie rzeczywiście wyniki są zwracane – w migracji aplikacji casewhen dodaję kilka wierszy do bazy.

Rozwiązanie zostało wdrożone w ramach integracji z Django REST Framework, ale chodzi tylko o zasadę działania, więc kod można wykorzystać także bez DRF.

Oczywiście całość do przejrzenia bezpośrednio na GitHub.

Wykonanie kodu przy starcie aplikacji w Django

Wielokrotnie zdarzyło mi się (i pewnie nie tylko mi) musieć uzupełnić jakimiś wartościami bazę danych w trakcie zmian w projekcie. Chodziło konkretnie o dane, które powinny się pojawić po wdrożeniu zmian i nie powinny dawać możliwości ich zmiany bądź usunięcia ze względu na potencjalny wpływ na działanie całej aplikacji.

Pierwszy pomysł tego rozwiązania to użycie migracji, jak w poniższym przykładzie:


from django.db import migrations
from ..models import PREDEFINED_TOASTR_TYPES
def preinstall_toastr(apps, schema_editor):
ToastrType = apps.get_model('toastr', 'ToastrType')
db_alias = schema_editor.connection.alias
for title, color in PREDEFINED_TOASTR_TYPES.items():
toastr = ToastrType.objects.create(
title=title,
color=color
)
toastr.save()
class Migration(migrations.Migration):
dependencies = [
('toastr', '0001_initial'),
]
operations = [
migrations.RunPython(
migrations.RunPython.noop,
preinstall_toastr,
),
]

Po wykonaniu migracji w bazie pojawią nam się wymagane wpisy. Jednak istotnym minusem tego rozwiązania jest to, że nadal można usunąć je bezpośrednio w bazie czy nawet w panelu administracyjnym Django i nie zostaną one odtworzone.

Kolejne podejście do problemu to przeniesienie kodu odpowiedzialnego za dodawanie wpisów do bazy do pliku apps.py, który uruchamiany jest przy starcie aplikacji – czyli praktycznie zawsze po restarcie serwera. Wrzucenie kodu do metody ready pozwoli nam obejść w całkiem elegancki sposób wspomniany wcześniej problem:


from django.apps import AppConfig
class ToastrConfig(AppConfig):
name = 'toastr'
def ready(self):
from .models import ToastrType, PREDEFINED_TOASTR_TYPES
# Prevent executing before applying migrations
if is_database_synchronized(DEFAULT_DB_ALIAS):
for title, color in PREDEFINED_TOASTR_TYPES.items():
try:
ToastrType.objects.get(title=title)
except ToastrType.DoesNotExist:
new_toastr_type = ToastrType(
title=title,
color=color
)
new_toastr_type.save()

view raw

apps.py

hosted with ❤ by GitHub

Na co zwrócić musimy uwagę, w pliku __init__.py naszej aplikacji musimy jawnie wskazać ścieżkę do pliku konfiguracyjnego aplikacji, jako że plik ten jest opcjonalny:


default_app_config = 'toastr.apps.ToastrConfig'

view raw

__init__.py

hosted with ❤ by GitHub

Warto także zerknąć na kilka innych kawałków kodu, m. in. uniemożliwienie edytowania kluczowych informacji w panelu administracyjnym Django czy usuwanie tych danych. Zauważyć też można 2 drobne usprawnienia: 1) wizualna reprezentacja zapisanego koloru w panelu administracyjnym dzięki użyciu funkcji format_html oraz 2) zmiana tytułu kolumny w panelu administracyjnym poprzez właściwość short_description.

Kawałek kodu związany z synchronizacją modeli z bazą danych został zaczerpnięty ze StackOverflow, a błąd dotyczący braku synchronizacji ujawnił się dopiero przy uruchomieniu testów.

Cały kod do pobrania i przejrzenia znaleźć można na GitHub.

Grupowanie podprojektów w PyCharm

Niezbyt często używaną funkcją PyCharma jest jego wsparcie dla dużych projektów w postaci możliwości dodawania nowych projektów w ramach bieżącego okna. Jeszcze mniej znaną (według mnie) funkcją pozwalającą zapanować nad wieloma projektami jest możliwość grupowania projektów w strukturze drzewa.

Co warto zrobić na początku – głównie z czysto estetycznych powodów – to ustalić jakiś całkowicie oddzielny katalog, który może pozostać pusty. Ewentualnie możemy trzymać jakieś „śmieci” związane z projektem, które nie muszą być, np. pod kontrolą wersji.

Zazwyczaj podczas tworzenia nowego projektu otwieramy go w nowym oknie PyCharm – i tak też zalecałbym zrobić dla dopiero co utworzonego katalogu „głównego”. To będzie nasz punkt startowy – a PyCharm będzie nam ładnie w tytule wyświetlał nowo powstały projekt główny.

Pozostałe projekty, które chcemy mieć widoczne w PyCharmie w odpowiedniej strukturze dodawać będziemy już w taki sposób:

Przechodząc do sedna sprawy wszystko rozbija się o nazewnictwo katalogów. Jeśli uporządkujemy projekty na dysku nadając im odpowiednie człony (prefixy) rozdzielane kropką, PyCharm zatroszczy się o resztę.

Po dodaniu kolejnych projektów będą w elegancki sposób pogrupowane w paczki, jak poniżej:

Nic więcej się nie zmienia i np. nadal posiadamy udogodnienie w postaci konfigurowania środowisk Pythona dla każdego projektu indywidualnie.

Oczywiście powyższe nie dotyczy osób, które są zwolennikami trzymania w PyCharm każdego projektu oddzielnie – jak zawsze ma to swoje plusy i minusy.

Śledzenie czasu spędzonego w IDE – integracja WakaTime i PyCharm

Nie tak dawno temu w ramach kolejnych prób ze śledzeniem swojego czasu spędzonego na pisaniu kodu trafiłem na WakaTime.
Dla moich potrzeb wystarczyła instalacja w PyCharm, ale inne wspierane środowiska programowania wraz z instrukcjami instalacji przejrzeć możemy na stronie WakaTime Editors.

Sama instalacja w PyCharm to tak naprawdę znalezienie i zainstalowanie wtyczki o nazwie – zgadza się – WakaTime. W opisie wtyczki jest dokładna instrukcja instalacji.

I w sumie to tyle. Po kilku godzinach pisania kodu możemy wejść na dashboard, gdzie zaprezentują nam się ładne statystyki, jakich języków używaliśmy najczęściej, w jakich projektach czy ile czasu danego dnia spędziliśmy w IDE.

Jak spora część narzędzi online także to ma swoje plany cenowe. Nam może wystarczyć darmowy, który pozwala m. in. na statystyki z ostatnich 2 tygodni. Otrzymujemy jednak narzędzie wakadump, które pozwala nam pobierać statystyki na wypadek, gdybyśmy chcieli je dalej wykorzystać.

Warto wspomnieć, że gdy z jakichś powodów coś się posypie ze środowiskiem lub będziemy chcieli zmienić klucz API to zmiany wystarczy wprowadzić w pliku: ~/.wakatime.cfg

Ukrywanie plików określonego typu w Visual Studio Code

O ile jestem wielkim zwolennikiem narzędzi ze stajni JetBrains to od czasu do czasu – gdy potrzebuję zapisać coś na szybko – zdarza mi się korzystać z innego całkiem przyjemnego edytora – VSCode.

Narzędzie to ma to do siebie, że realne przyjemności zaczynają się dopiero, gdy skonfigurujemy je po swojemu i na szczęście sprawa jest dużo prostsza niż w Vim-ie :).

Po otwarciu w edytorze jakiegokolwiek projektu możemy zauważyć (albo raczej nie zauważyć), że w strukturze katalogów nie pojawiają się katalogi kilku powszechnie używanych systemów kontroli wersji: .git, .svn, .hg. Ukrywane są też pliki .DS_Store. Sprawa fajna i przydatna, ale jeśli zdarzy nam się pisać projekt, np. w Angularze to szybko dojdziemy do wniosku, że drzewo katalogów jest zaśmiecone i nieczytelne, m.in. ze względu na automatycznie generowane pliki – których w edytorze i tak najczęściej nie wykorzystamy.

Pozostaje nam zmienić odrobinę ustawienia edytora. Sama konfiguracja to po prostu edycja plików; dostęp do mich uzyskujemy poprzez File > Preferences > Settings (lub bezpośrednio z klawiatury Ctrl + ,).
W lewym panelu znajdziemy listę ustawień domyślnych, z prawej – ustawienia użytkownika. I właśnie ta prawa część interesuje nas najbardziej. A to co chcemy zrobić to nadpisać wartość dla atrybutu "files.exclude".

Z mojej strony chciałbym zaproponować ukrycie w drzewie katalogów dodatkowych 2 typów plików: *.js.map oraz plików *.js – o ile istnieje dla nich plik o takiej samej nazwie ale rozszerzeniu *.ts.


{
// …
"files.exclude": {
      "**/.git": true,
      "**/.svn": true,
      "**/.hg": true,
      "**/CVS": true,
      "**/.DS_Store": true,
      "**/*.js.map": true,
      "**/*.js": {"when": "$(basename).ts"}
  },
// …
}

Dla kompletnie zielonych – interesują nas linie 9 i 10 :).

I jeszcze na koniec taka mała wskazówka, jeśli posiadamy otwartego, np. PyCharma (nie wiem jeszcze, czy inne IDE JetBrains posiadają taki sam skrót klawiaturowy) to – dzięki tylko 4 palcom – możemy otworzyć edytor-notatkę z konkretnym podświetlaniem składni bez tworzenia nowego pliku w strukturze projektu: Ctrl + Alt + Shift + Insert.