Nasza strona używa cookies. Dowiedz się więcej o celu ich używania i zmianie ustawień w przeglądarce. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Ściąga z robienia pakietów w Pythonie

Ricardo Mendes Senior Software Engineer / Google Cloud Certified Architect / CI&T
Sprawdź, jak stworzyć i skonfigurować czystą strukturę pakietu w Pythonie.
Ś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='you@yourdomain.com',
    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ę od package_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ąpieniu requirements.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 do setup.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ługi pytest 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 odpalenia pytest. 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.

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 120 tysiącami naszych czytelników

Dowiedz się więcej