Ściąga z robienia pakietów w Pythonie
Według deweloperów Python znajduje się w czołówce języków programowania w 2019 roku. Biorąc pod uwagę siłę społeczności open source i wysoki poziom adopcji w rozwijających się dziedzinach, takich jak big data, analityka i uczenie maszynowe, nikt nie powinien być zaskoczony jego rosnącą popularnością. Liczba pakietów dostępnych dla programistów Pythona, również będzie rosła. Możesz wziąć odpowiedzialność za niektóre z nich.
Jeśli się na to zdecydujesz, pamiętaj, że Python jest bardzo elastyczny pod względem możliwości konfiguracji pakietu. W sieci istnieje wiele źródeł, które to potwierdzają. Mając jednak tak dużo opcji do wyboru, ta kwestia może zacząć się mącić. Zwłaszcza podczas rozpoczynania przygody z tworzeniem i dystrybucją pakietów.
Celem tego artykułu jest opisanie czystej struktury pakietu, ułatwiającej programistom testowanie, budowanie i publikowanie go oraz pisanie jak najmniejszej konfiguracji przy jednoczesnym wykorzystaniu różnych konwencji.
Struktura pakietu
Proponowane przeze mnie foldery i pliki, wraz z elementami służącymi do testowania:
project-root
├──src/
└──package_name/
├──tests/
└──package_name/
├──.coveragerc
├──.gitignore
├──LICENSE
├──pytest.ini
├──README.md
├──setup.cfg
└──setup.py
Omówię je wszystkie, poza .gitignore
, LICENSE
i README.md
, ponieważ są to pliki powszechnie znane. Zapytaj wujka Google, jeśli masz odnośnie ich jakieś pytania.
Zacznę od setup.py
, który opisuje pakiet. Składa się ze skryptu Pythona, w którym można ustawić deklaratywnie wiele właściwości, jak pokazano poniżej. Właściwości zadeklarowane w tym pliku są rozpoznawane przez menedżery pakietów, takie jak pip oraz IDE, jak PyCharm, co oznacza, że jest to obowiązkowy element każdego pakietu.
from setuptools import find_namespace_packages, setup
packages = [package for package in find_namespace_packages(where='./src', include='package_cheat_sheet.*')]
setup(
name='python-package-cheat-sheet',
version='1.0.0',
author='Your Name',
author_email='[email protected]',
description='Python package developer\'s cheat sheet',
platforms='Posix; MacOS X; Windows',
packages=packages,
package_dir={
'': 'src'
},
include_package_data=True,
install_requires=(
'stringcase',
),
classifiers=[
'Development Status :: 1 - Alpha',
'Natural Language :: English',
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
)
plik konfiguracji początkowej
Znaczenie niektórych właściwości jest dość proste: name
, version
, author
… ale inne wymagają nieco wyjaśnienia:
packages
,package_dir
: służy do ustalania lokalizacji plików źródłowych pakietu i deklarowanych przez nich przestrzeni nazw. W powyższym przykładzie src jest skonfigurowany jako źródłowy folder główny (jeśli ma podfoldery, domyślnie są one zawarte w pakiecie). Takie pliki deklarują przestrzenie nazw zaczynające się odpackage_cheat_sheet
install_requires
: krotka ze wszystkimi zależnościami, których Twój pakiet potrzebuje do pracy - ostrzeżenie: dodawaj tylko zależności operacyjne. Nie należy tu umieszczać niczego, co związane jest z testowaniem lub budowaniem (zależności testowe opisuję w następnej sekcji). Możesz też pomyśleć o tym, jako o częściowym zastąpieniurequirements.txt
, jeśli go używasz.
Te właściwości pozwalają nam pakować tylko pliki potrzebne użytkownikom podczas pracy z dostarczanymi przez nas pakietami, co skutkuje mniejszymi plikami dystrybucyjnymi. Ponadto pobrane zostaną tylko te zależności, które każdy pakiet musi uruchomić, unikając niepotrzebnego zużycia sieci i pamięci.
Jest praktyka, która pozwala sprawdzić, jak to działa. Przykładowy kod z tego artykułu jest dostępny na GitHubie, a pip
pozwala nam instalować hostowane tam pakiety. Zainstaluj Python 3.6+ i aktywuj virtualenv. Następnie:
pip install git+https://github.com/ricardolsmendes/python-package-cheat-sheet
pip freeze
Zainstalowane zostały dwa pakiety:
python-package-cheat-sheet==1.0.0
stringcase==1.2.0
Pierwszy zadeklarowano w pliku setup.py
, dostępnym w repozytorium na GitHubie. Drugi to wymagana (operacyjna) zależność dla pierwszego pakietu.
Teraz wywołajmy metodę package_cheat_sheet.StringFormatter.format_to_snakecase
przy użyciu powłoki interaktywnej Python:
python
>>> from package_cheat_sheet import StringFormatter
>>> print(StringFormatter.format_to_snakecase('FooBar'))
foo_bar
>>> exit()
Jak widać, foo_bar
jest wyjściem StringFormatter.format_to_snakecase ('FooBar')
, co oznacza, że instalacja pakietu działa zgodnie z oczekiwaniami. To krótka demonstracja tego, jak skonfigurować pakiet Python i udostępnić go użytkownikom dzięki kilku liniom kodu.
Pakiety również wymagają zautomatyzowanych testów
Nowoczesne oprogramowanie opiera się na testach automatycznych i nie powinno się nawet myśleć o rozpoczęciu tworzenia pakietu Python bez nich. Pytest jest najczęściej używaną do tego biblioteką, więc zobaczmy, jak zintegrować ją z konfiguracją pakietu.
W pierwszym ćwiczeniu weszliśmy w rolę użytkownika - teraz czas będziemy programistami.
W pierwszej kolejności odinstaluj pakiet pobrany z GitHuba, sklonuj pełny przykładowy kod i zainstaluj ponownie z lokalnych źródeł:
pip uninstall python-package-cheat-sheet
git clone https://github.com/ricardolsmendes/python-package-cheat-sheet.git
cd python-package-cheat-sheet
pip install --editable .
Poleceniem wywołującym zestaw testów na podstawie pliku instalacyjnego, jest python setup.py test
. Pytest nie jest używany domyślnie, ale istnieje sposób na zastąpienie nim domyślnego narzędzia testowego: Utwórz plik setup.cfg
w folderze głównym pakietu, ustawiając alias dla polecenia test
.
[aliases]
test=pytest
- Od tej chwili wymagane są zależności
pytest
. W przeciwnym razie polecenie nie powiedzie się po utworzeniu aliasu. Zależności zostaną dodane dosetup.py
przy użyciu różnych właściwości:
setup_requires = (
'pytest-runner',
),
tests_require = (
'pytest-cov',
)
pytest-runner
jest odpowiedzialny za dodanie obsługipytest
dla zadań instalacyjnych.pytest-cov
pomoże nam wygenerować statystyki pokrycia naszego kodu.
Jeszcze dwa pliki konfiguracyjne muszą znajdować się w folderze głównym pakietu: pytest.ini
i .coveragerc
:
pytest.ini
zawiera dodatkowe parametry do odpaleniapytest
. Prezentuje np. wyniki pokrycia zarówno w konsoli, jak i plikach HTML:
[pytest]
addopts=--cov --cov-report html --cov-report term-missing
.coveragerc
kontroluje zasięg skryptu do wyznaczania pokrycia. Jest to bardzo przydatne, gdy masz w swoim projekcie foldery, które nie muszą być monitorowane przez takie narzędzie. W proponowanej czystej strukturze należy uwzględnić tylko folder src:
[run]
source =
src
Jesteśmy gotowi do uruchomienia testu python setup.py, teraz obsługiwanego przez pytest. Pytest domyślnie szuka plików testowych w folderze z testami. Dla sklonowanego repozytorium GitHub oczekiwane dane wyjściowe to:
plugins: cov-2.7.1
collected 10 items
tests\package_cheat_sheet\string_formatter_test.py .......... [100%]
----------- coverage: platform win32, python 3.7.2-final-0 ---------
Name Stmts Miss Cover Missing
--------------------------------------------------------------------
src\package_cheat_sheet\__init__.py 1 0 100%
src\package_cheat_sheet\string_formatter.py 17 0 100%
tests\package_cheat_sheet\string_formatter_test.py 31 0 100%
--------------------------------------------------------------------
TOTAL 49 0 100%
Coverage HTML written to dir htmlcov
Po uruchomieniu pytest
sprawdź także htmlcov/index.htm
, ponieważ wyjście HTML bardzo pomaga, gdy chcesz lepiej zrozumieć raporty pokrycia.
pytest-cov HTML output
Podsumowanie
W tym artykule przedstawiłem czystą strukturę pakietu Python, obejmującą zarówno ogólne ustawienia, jak i elementy testowe. Zaproponowałem wyraźne oddzielenie źródeł i plików testowych, używając standardów Pythona, podejścia convention over configuration i popularnych narzędzi, aby zadanie było jak najmniej pracochłonne.
Oryginał tekstu w języku angielskim przeczytasz tutaj.