Moduly

Zatím jsme tvořili programy v Pythonu tak nějak na divoko, tedy v jednom nebo více souborech bez nějakého zvláštního řádu. V této lekci se podíváme na to, jak tvořit redistribuovatelné moduly, které jdou nahrát na PyPI (veřejný seznam pythonních balíčků) a instalovat pomocí pipu.

Za příklad si vezmeme kód Ondřeje Caletky, který umožňuje určit české svátky v zadaném roce. Jako příklad je ideální, protože obsahuje jak funkce, které můžeme volat z Pythonu, tak lze volat z příkazové řádky.

Volání z příkazové řádky, pomocí příkazu python isholiday.py nebo python -m isholiday, zajišťuje blok if __name__ == '__main__':. Toto je rychlý způsob, jak napsat modul který jde jak importovat, tak spustit. Když nějaký modul importujeme, má v proměnné __name__ k dispozici své jméno. „Hlavní” modul ale není importován a jeho jméno není vždy k dispozici (např. v cat isholiday.py | python). Python proto __name__ „hlavního” modulu nastavuje na '__main__', čehož se často využívá.

Později se podíváme na elegantnější způsob jak to zařídit; teď se vraťme zpět k balíčkování.

setup.py

Základním stavebním kamenem Python balíčku je soubor setup.py, který obsahuje všechna potřebná metadata ve volání funkce setup() z modulu setuptools.

Pojďme vytvořit jeho minimální variantu:

from setuptools import setup


setup(
    name='isholiday',
    version='0.1',
    description='Finds Czech holiday for given year',
    author='Ondřej Caletka',
    author_email='ondrej@caletka.cz',
    license='Public Domain',
    url='https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e',
    py_modules=['isholiday'],
)

Všimněte si, že jsme balíček pojmenovali stejně jako soubor se zdrojovým kódem. Je to dobrá konvence, ale není to technicky nutné.

Balíček můžeme zkusit nainstalovat do virtualenvu:

$ python3.6 -m venv __venv__     # (nebo jinak -- podle vašeho OS)
$ . __venv__/bin/activate        # (nebo jinak -- podle vašeho OS)
(__venv__)$ python setup.py install
...
(__venv__)$ python
>>> import isholiday
>>> 
(__venv__)$ python -m pip freeze
isholiday==0.1

Přes setup.py můžeme dělat další věci, například vytvořit archiv s balíčkem:

(__venv__)$ python setup.py sdist
...
warning: sdist: standard file not found: should have one of README, README.rst, README.txt
...

Extra soubory do zdrojového balíčku

Jak vidíte, setuptools si stěžuje, že náš projekt nemá README – soubor, do kterého se tradičně píšou základní informace o projektu. Můžeme jej vytvořit a uložit jako README přímo v kořenovém adresáři projektu, tedy tam, kde byste jej nejspíš čekali.

Czech public holiday checker...

Poté spustíme setup.py sdist znovu:

(__venv__)$ python setup.py sdist

V adresáři dist najdete archiv, jeho obsah můžete zkontrolovat. Měl by tam být i soubor README.

Skvělé, pojďme vytvořit i další speciální soubor, LICENSE, který bude obsahovat text licence, v tomto případě Public Domain. Obsah najdete třeba na CC0.

Pokud ale se souborem LICENSE vytvoříte zdrojový balíček, soubor v archivu nebude. Je to proto, že se standardně do archivu přidávají jen některé soubory. Další soubory lze přidat pomocí souboru MANIFEST.in, dle dokumentace.

V našem případě bude MANIFEST.in vypadat takto:

include LICENSE

Při dalším spuštění už setup.py přidá i soubor LICENSE. To můžete zkontrolovat i ve výsledném archivu.

(__venv__)$ python setup.py sdist
...
hard linking LICENSE -> isholiday-0.1
hard linking MANIFEST.in -> isholiday-0.1
hard linking README -> isholiday-0.1
...

Hotový balíček pak můžete nainstalovat pomocí nástroje pip. Doporučuji to dělat v jiném virtuálním prostředí – v aktuálním už ho máte nainstalovaný.

# v jiné konzoli, v jiném adresáři
$ python3 -m venv __venv2__
$ . __venv2__/bin/activate
(__venv2__)$ python -m pip install cesta/k/projektu/dist/isholiday-0.1.tar.gz
Processing cesta/k/projektu/dist/isholiday-0.1.tar.gz
Installing collected packages: isholiday
  Running setup.py install for isholiday ... done
Successfully installed isholiday-0.1

Více argumentů pro setup()

Na chvíli se vrátíme k volání funkce setup() a přidáme co nejvíc dalších položek. Jejich vysvětlení najdete v dokumentaci.

from setuptools import setup


with open('README') as f:
    long_description = ''.join(f.readlines())


setup(
    name='isholiday',
    version='0.1',
    description='Finds Czech holiday for given year',
    long_description=long_description,
    author='Ondřej Caletka',
    author_email='ondrej@caletka.cz',
    keywords='holiday,dates',
    license='Public Domain',
    url='https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e',
    py_modules=['isholiday'],
    classifiers=[
        'Intended Audience :: Developers',
        'License :: Public Domain',
        'Operating System :: POSIX :: Linux',
        'Programming Language :: Python',
        'Programming Language :: Python :: Implementation :: CPython',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.5',
        'Topic :: Software Development :: Libraries',
        ],
    zip_safe=False,
)

Všimněte si několika věcí. V první řadě v long_description vidíte, že jsme pořád ještě v Pythonu a můžeme si ušetřit duplikaci nějakých informací pomocí malého kousku kódu. Dalším zajímavým argumentem je classifiers. Jsou to v podstatě takové tagy nebo strukturované informace o balíčku. Zásadně si je nevymýšlíme sami, ale hledáme je v seznamu. Tyto informace budou později vidět na PyPI a půjde podle nich hledat.

Argument zip_safe=False zajistí, že se modul nainstaluje do adresáře. Setuptools totiž mají nepříjemný zlozvyk instalovat moduly jako zip, což komplikuje práci s datovými soubory (např. templates pro Flask). Je proto lepší zip_safe=False uvést.

Více souborů s Python kódem

Doteď jsme vytvářeli balíček jen z jednoho zdrojového souboru isholiday.py. Co ale dělat, pokud je náš projekt větší a obsahuje souborů více? Teoreticky je možné je přidat všechny do py_modules, ale není to dobrý nápad.

Proč to vlastně není dobrý nápad? Jednotlivé moduly ze všech nainstalovaných balíčků by byly rozesety bez ladu a skladu mezi ostatními. Mohl by snadno nastat konflikt v názvech, například pokud by více balíčků mělo modul utils. Slušně vychovaný Pythonista dá do každého balíčku právě jeden modul, pojmenovaný stejně jako balíček.

Raději uděláme modul ve formě složky. V našem případě soubor isholiday.py zatím přesuneme do isholiday/__init__.py:

(__venv__)$ tree
.
├── isholiday
│   └── __init__.py
├── LICENSE
├── MANIFEST.in
├── README
└── setup.py

1 directory, 5 files

Soubor __init__.py jednak značí, že adresář isholiday je pythonní modul, a také obsahuje kód, který se spustí při importu modulu isholiday.

Musíme ještě mírně upravit setup.py – místo py_modules použijeme packages:

diff --git a/setup.py b/setup.py
index 3a69792..6b453ab 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@ setup(
     keywords='holiday,dates',
     license='Public Domain',
     url='https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e',
-    py_modules=['isholiday'],
+    packages=['isholiday'],
     classifiers=[
         'Intended Audience :: Developers',
         'License :: Public Domain',

Případně, což je ještě lepší, můžeme použít find_packages():

from setuptools import setup, find_packages

setup(
    ...
    packages=find_packages(),
    ...
)

A jaký je tedy vlastně rozdíl mezi py_modules a packages? Zjednodušeně: Ten první je na soubory, ten druhý na adresáře.

Momentálně máme všechen kód přímo v __init__.py, což sice funguje, ale ideální to není. Dobré je mít kód v samostatných souborech a v __init__.py pouze importovat veřejné rozhraní, tedy to, co budou z vašeho modulu importovat jeho uživatelé.

V souboru __init__.py by tak prakticky žádný kód kromě importů být neměl. Přesuňte tedy obsah __init__.py do holidays.py a do __init__.py místo toho napište:

from .holidays import getholidays, isholiday

__all__ = ['getholidays', 'isholiday']

Tečka v příkazu import není chyba: je to zkratka pro aktuální modul. Můžeme psát i from isholiday.holidays import ..., což ale trochu ztěžuje případné přejmenování modulu.

Ono __all__ pak explicitně definuje rozhraní modulu. Například s původním modulem šlo provést from isholiday import datetime, ale asi by nikdo nečekal, že tahle možnost bude nutně zachována i v příštích verzích knihovny. Seznamem __all__ dáte najevo, že tyhle funkce nejsou jen „náhodné importy“, a zároveň tím zamezíte různým varováním o importovaném ale nevyužitém modulu, které může hlásit vaše IDE nebo linter.

Python samotný pak __all__ používá jako seznam proměnných importovaných přes from isholiday import * Tento způsob importu nevidíme rádi, protože znepřehledňuje kód, to ale neznamená, že to musíme uživatelům naší knihovny znepříjemňovat (např. pro interaktivní režim).

Spouštění balíčku

Pokusíme-li se teď program spustit pomocí python -m isholiday, narazíme na problém: na rozdíl od souboru se složka s kódem takto spustit nedá:

$ python -m isholiday
python: No module named isholiday.__main__; 'isholiday' is a package and cannot be directly executed

Namísto spuštění souboru (typicky s blokem if __name__ == '__main__':) totiž Python v tomto případě hledá soubor pojmenovaný __main__.py a spustí ten.

Soubor __main__.py není určený k tomu, aby se z něho importovalo, proto by měl obsahovat co nejméně kódu – ideálně jen volání funkce, která je definovaná jinde. Vytvořte proto __main__.py s následujícím obsahem:

from .holidays import main

main()

a v holidays.py zaměňte if __name__ == '__main__': za def main():.

Skript teď bude možné použít pomocí python -m isholiday. Bude to fungovat i tehdy, když vytvoříte balíček (python setup.py sdist) a nainstalujete ho v jiném virtuálním prostředí.

Programy pro příkazovou řádku

Pokud chcete, aby váš modul umožňoval spouštění přímo z příkazové řádky, bez python -m, měli byste použít entrypoints. K tomu je potřeba přidat do volání setup v setup.py příslušný argument:

setup(
    ...
    entry_points={
        'console_scripts': [
            'isholiday_demo = isholiday.holidays:main',
        ],
    },
)

isholiday_demo je jméno entrypointu, tedy příkazu pro příkazovou řádku. isholiday.holidays:main je pak cesta k funkci ve tvaru modul:funkce; funkce může být v modulu definovaná nebo importovaná.

Skript bude možné použít, je-li aktivní prostředí, kde je nainstalován, jen zadáním jména entrypointu:

(__venv__)$ python setup.py sdist
# v jiné konzoli, v jiném virtuálním prostředí
(__venv2__)$ python -m pip install --upgrade cesta/k/projektu/dist/isholiday-0.1.tar.gz
(__venv2__)$ isholiday_demo
...
Mon Mar 28 00:00:00 2016 True
Tue Mar 28 00:00:00 2017 False
Fri Apr 14 00:00:00 2017 True

Specifikace závislostí

Balíčky na PyPI mohou záviset na dalších balíčkách. V případě isholiday to potřeba není, ale v úlohách z minulých cvičení ano.

Existuje několik úrovní závislostí, ve většině případů si vystačíte s argumentem install_requires. Balíček, který závisí na knihovnách Flask (jakékoli verze) a click (verze 6 a vyšší) by v setup.py měl mít:

setup(
    ...
    install_requires=['Flask', 'click>=6'],
)

Soubor requirements.txt

Kromě závislostí v setup.py se u pythonních projektů často setkáme se souborem requirements.txt, který obsahuje přesné verze všech závislostí, včetně tranzitivních – t.j. závisí-li náš balíček na Flask a Flask na Jinja2, najdeme v requirements.txt mimojiné řádky:

Flask==0.11.1
Jinja2==2.8

Tento soubor se používá, když je potřeba přesně replikovat prostředí, kde program běží, například mezi testovacím strojem a produkčním nasazením webové aplikace. Tento soubor se dá vygenerovat z aktuálního prostředí zadáním python -m pip freeze > requirements.txt a balíčky v něm se dají nainstalovat pomocí python -m pip install -r requirements.txt. My ho používat nebudeme, vystačíme si s volnější specifikací závislostí v setup.py.

Upload na PyPI

Balíček jde zaregistrovat a nahrát na PyPI. Původně k tomu sloužily příkazy setup.py register a upload, ale tyto příkazy používají HTTP, což není bezpečné. Prototo je lepší použít program twine (instalovatelný přes pip), který používá HTTPS.

Budete si potřebovat zařídit účet na PyPI, účet na testovací PyPI a vytvořit konfigurační soubor ~/.pypirc:

[distutils]
index-servers=
    pypi
    testpypi

[pypi]
username = <your user name goes here>
password = <your password goes here>

[testpypi]
repository = https://test.pypi.org/legacy/
username = <your user name goes here>
password = <your password goes here>

Hesla můžete vynechat, pokud je budete chtít pokaždé zadávat.

Používáte-li Windows, je potřeba nastavit proměnnou prostředí HOME na adresář se souborem .pypirc, např:

> set HOME=C:\cesta\k\nastaveni

Registrace projektu a nahrání na testovací PyPI se provádí pomocí příkazu upload: ten projekt zaregistrueje (pokud to jde) a nahraje samotný balíček:

(__venv__)$ twine upload -r testpypi dist/isholiday-0.1.tar.gz
Uploading distributions to https://test.pypi.org/legacy/
Uploading isholiday-0.1.tar.gz
[================================] 8379/8379 - 00:00:02

První nahrání se zdaří, jen pokud jméno projektu již není zabrané. Další nahrávání je povoleno jen vám, případně uživatelům, kterým to povlíte přes webové rozhraní. Po úspěšném nahrání lze nahrávat další verze modulu, ale musí být novější než ta, co už na PyPI je. Nejde tedy jednou nahraný modul přepsat.

Svůj balíček najdete na https://test.pypi.org/project/<název_balíčku>/.

Pro nahrání na opravdovou PyPI stačí vynechat -r testpypi. Zabírat jména na opravdové PyPI jen tak není hezké vůči ostatním Pythonistům; registrujte tedy prosím jen moduly, které budou nějak pro ostatní užitečné.

Instalace pomocí pip

Projekt nahraný na PyPI by mělo jít nainstalovat pomocí pipu. V případě použití ostré verze PyPI stačí k instalaci zadat název balíčku:

(__venv__)$ python -m pip install <název_balíčku>

Pokud však použijeme testovací PyPI, je nutné pipu říct, aby balíček hledal tam. Postup uvedený v dokumentaci není v tomto případě nejvhodnější, protože z testovací PyPI vezme jak náš balíček, tak i případné závislosti, které mohou být zastaralé, rozbité či jinak škodlivé.

Lepší by bylo, kdyby pip nainstaloval závislosti z ostré PyPI a na testovací hledal jen náš projekt. Toho se dá docílit přepínačem --extra-index-url.

(__venv__)$ python -m pip install --extra-index-url https://test.pypi.org/pypi <název_balíčku>

V tomto případě pip nejdřív prohledá ostrou PyPI, a pokud nenajde požadovaný balíček, použije testovací PyPI. Zde je potřeba dávat pozor na název projektu, protože případné konflikty mezi ostrou a testovací PyPI se nekontrolují. Pokud tedy máme projekt na testovací PyPI a na ostré existuje projekt se stejným názvem, nainstaluje se ten z ostré verze.

V případě, že tento problém nastane, je možné ho částečně obejít specifikací verze instalovaného balíčku:

(__venv__)$ python -m pip install --extra-index-url https://test.pypi.org/pypi <název_balíčku>==0.3

Pokud u duplicitního projektu na ostré PyPI neexistuje požadovaná verze, nainstaluje se náš projekt z testovací PyPI.

Jiná možnost je zadat přímo cestu k archivu s balíčkem místo jeho názvu. Zde pak na umístění balíčku ani verzi nezáleží:

(__venv__)$ python -m pip install https://test-files.pythonhosted.org/packages/.../<název_balíčku>-0.3.tar.gz

Archiv se dá najít na informační stránce o našem projektu na PyPI.

Datové soubory

Některé balíčky kromě samotného kódu potřebují i datové soubory. Například aplikace ve Flasku potřebují templates. Taková data se dají přidat parametrem package_data:

setup(...,
    packages=['hello_flask'],
    ...
    package_data={'hello_flask': ['templates/*.html']},
)

Další informace jsou odkázané v dokumentaci.

Wheel

Zatím jsme se zabývali jen zdrojovými balíčky sdist (source distribution). Existují ale i balíčky „zkompilované” – bdist (binary distribution). Když se instaluje zdrojový balíček, vykonává se kód ze souboru setup.py. Binární balíček se místo toho jen rozbalí na patřičné místo.

Z historických důvodů existuje několik různých druhů binárních distribucí, v současné době je ale důležitá pouze možnost bdist_wheel:

(__venv__)$ python setup.py bdist_wheel

Výsledek je v souboru dist/*.whl.

Pokud vám příkaz nefunguje, nainstalujte balík wheel.

Obsah wheelu můžete prozkoumat, je to obyčejný ZIP.

Naše programy jsou zatím platformně nezávislé a ve wheelu, i když se jmenuje binární, žádné binární soubory nejsou. To se ale změní, až se budeme zabývat tvorbou modulů v jazyce C: sdist pak obsahuje zdrojové soubory a bdist_wheel zkompilované moduly.

Potom je dobré distribuovat oba dva – každý má své výhody:

  • sdist jde nainstalovat na různých operačních systémech i procesorových architekturách,
  • sdist tradičně obsahuje soubory jako LICENSE a README, ale
  • wheel při instalaci nepotřebuje např. překladače C (všechno už je přeložené pro konkrétní OS a architekturu), a
  • wheel se rychleji instaluje.

Proces vydání složitějšího softwaru pak může vypadat takto:

(__venv__)$ rm dist/*
(__venv__)$ python setup.py sdist bdist_wheel
[... kontrola vytvořených balíčků v „čistém“ virtualenvu ...]
(__venv__)$ python -m twine upload dist/*

Další

K balíčkování existuje obsáhlá dokumentace. Budete-li chtít dělat něco, co v tomto kurzu není, podívejte se tam!

{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2017/mipyt-zima:distribution:0",
      "title": "Moduly",
      "html": "\n          \n    \n\n    <h1>Moduly</h1>\n<p>Zat&#xED;m jsme tvo&#x159;ili programy v Pythonu tak n&#x11B;jak na divoko, tedy v jednom nebo\nv&#xED;ce souborech bez n&#x11B;jak&#xE9;ho zvl&#xE1;&#x161;tn&#xED;ho &#x159;&#xE1;du. V t&#xE9;to lekci se pod&#xED;v&#xE1;me na\nto, jak tvo&#x159;it redistribuovateln&#xE9; moduly, kter&#xE9; jdou nahr&#xE1;t na PyPI (ve&#x159;ejn&#xFD;\nseznam pythonn&#xED;ch bal&#xED;&#x10D;k&#x16F;) a instalovat pomoc&#xED; pipu.</p>\n<p>Za p&#x159;&#xED;klad si vezmeme k&#xF3;d Ond&#x159;eje Caletky, kter&#xFD; umo&#x17E;&#x148;uje ur&#x10D;it &#x10D;esk&#xE9; sv&#xE1;tky\nv zadan&#xE9;m roce. Jako p&#x159;&#xED;klad je ide&#xE1;ln&#xED;, proto&#x17E;e obsahuje jak funkce, kter&#xE9;\nm&#x16F;&#x17E;eme volat z Pythonu, tak lze volat z p&#x159;&#xED;kazov&#xE9; &#x159;&#xE1;dky.</p>\n<ul>\n<li><a href=\"https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e\">oskar456/isholiday.py</a></li>\n</ul>\n<p>Vol&#xE1;n&#xED; z p&#x159;&#xED;kazov&#xE9; &#x159;&#xE1;dky, pomoc&#xED; p&#x159;&#xED;kazu <code>python isholiday.py</code> nebo\n<code>python -m isholiday</code>, zaji&#x161;&#x165;uje blok <code>if __name__ == &apos;__main__&apos;:</code>.\nToto je rychl&#xFD; zp&#x16F;sob, jak napsat modul kter&#xFD; jde jak importovat, tak spustit.\nKdy&#x17E; n&#x11B;jak&#xFD; modul importujeme, m&#xE1; v prom&#x11B;nn&#xE9; <code>__name__</code> k dispozici sv&#xE9; jm&#xE9;no.\n&#x201E;Hlavn&#xED;&#x201D; modul ale nen&#xED; importov&#xE1;n a jeho jm&#xE9;no nen&#xED; v&#x17E;dy k dispozici\n(nap&#x159;. v <code>cat isholiday.py | python</code>).\nPython proto <code>__name__</code> &#x201E;hlavn&#xED;ho&#x201D; modulu nastavuje na <code>&apos;__main__&apos;</code>,\n&#x10D;eho&#x17E; se &#x10D;asto vyu&#x17E;&#xED;v&#xE1;.</p>\n<p>Pozd&#x11B;ji se pod&#xED;v&#xE1;me na elegantn&#x11B;j&#x161;&#xED; zp&#x16F;sob jak to za&#x159;&#xED;dit; te&#x10F; se vra&#x165;me\nzp&#x11B;t k bal&#xED;&#x10D;kov&#xE1;n&#xED;.</p>\n<h2>setup.py</h2>\n<p>Z&#xE1;kladn&#xED;m stavebn&#xED;m kamenem Python bal&#xED;&#x10D;ku je soubor <code>setup.py</code>, kter&#xFD;\nobsahuje v&#x161;echna pot&#x159;ebn&#xE1; metadata ve vol&#xE1;n&#xED; funkce <code>setup()</code> z modulu\n<code>setuptools</code>.</p>\n<p>Poj&#x10F;me vytvo&#x159;it jeho minim&#xE1;ln&#xED; variantu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">setuptools</span> <span class=\"kn\">import</span> <span class=\"n\">setup</span>\n\n\n<span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&apos;isholiday&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">version</span><span class=\"o\">=</span><span class=\"s1\">&apos;0.1&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">description</span><span class=\"o\">=</span><span class=\"s1\">&apos;Finds Czech holiday for given year&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">author</span><span class=\"o\">=</span><span class=\"s1\">&apos;Ond&#x159;ej Caletka&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">author_email</span><span class=\"o\">=</span><span class=\"s1\">&apos;ondrej@caletka.cz&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">license</span><span class=\"o\">=</span><span class=\"s1\">&apos;Public Domain&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">url</span><span class=\"o\">=</span><span class=\"s1\">&apos;https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">py_modules</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s1\">&apos;isholiday&apos;</span><span class=\"p\">],</span>\n<span class=\"p\">)</span>\n</pre></div><p>V&#x161;imn&#x11B;te si, &#x17E;e jsme bal&#xED;&#x10D;ek pojmenovali stejn&#x11B; jako soubor se zdrojov&#xFD;m k&#xF3;dem.\nJe to dobr&#xE1; konvence, ale nen&#xED; to technicky nutn&#xE9;.</p>\n<p>Bal&#xED;&#x10D;ek m&#x16F;&#x17E;eme zkusit nainstalovat do virtualenvu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python3.6 -m venv __venv__     <span class=\"c1\"># (nebo jinak -- podle va&#x161;eho OS)</span>\n<span class=\"gp\">$ </span>. __venv__/bin/activate        <span class=\"c1\"># (nebo jinak -- podle va&#x161;eho OS)</span>\n<span class=\"gp\">(__venv__)$ </span>python setup.py install\n<span class=\"go\">...</span>\n<span class=\"gp\">(__venv__)$ </span>python\n<span class=\"gp\">&gt;</span>&gt;&gt; import isholiday\n<span class=\"gp\">&gt;</span>&gt;&gt; \n<span class=\"gp\">(__venv__)$ </span>python -m pip freeze\n<span class=\"go\">isholiday==0.1</span>\n</pre></div><p>P&#x159;es <code>setup.py</code> m&#x16F;&#x17E;eme d&#x11B;lat dal&#x161;&#xED; v&#x11B;ci, nap&#x159;&#xED;klad vytvo&#x159;it archiv s bal&#xED;&#x10D;kem:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python setup.py sdist\n<span class=\"go\">...</span>\n<span class=\"go\">warning: sdist: standard file not found: should have one of README, README.rst, README.txt</span>\n<span class=\"go\">...</span>\n</pre></div><h2>Extra soubory do zdrojov&#xE9;ho bal&#xED;&#x10D;ku</h2>\n<p>Jak vid&#xED;te, <code>setuptools</code> si st&#x11B;&#x17E;uje, &#x17E;e n&#xE1;&#x161; projekt nem&#xE1; <code>README</code> &#x2013; soubor,\ndo kter&#xE9;ho se tradi&#x10D;n&#x11B; p&#xED;&#x161;ou z&#xE1;kladn&#xED; informace o projektu.\nM&#x16F;&#x17E;eme jej vytvo&#x159;it a ulo&#x17E;it jako <code>README</code> p&#x159;&#xED;mo v ko&#x159;enov&#xE9;m adres&#xE1;&#x159;i projektu,\ntedy tam, kde byste jej nejsp&#xED;&#x161; &#x10D;ekali.</p>\n<div class=\"highlight\"><pre><code>Czech public holiday checker...</code></pre></div><p>Pot&#xE9; spust&#xED;me <code>setup.py sdist</code> znovu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python setup.py sdist\n</pre></div><p>V adres&#xE1;&#x159;i <code>dist</code> najdete archiv, jeho obsah m&#x16F;&#x17E;ete zkontrolovat. M&#x11B;l by tam\nb&#xFD;t i soubor <code>README</code>.</p>\n<p>Skv&#x11B;l&#xE9;, poj&#x10F;me vytvo&#x159;it i dal&#x161;&#xED; speci&#xE1;ln&#xED; soubor, <code>LICENSE</code>, kter&#xFD; bude\nobsahovat text licence, v tomto p&#x159;&#xED;pad&#x11B; Public Domain.\nObsah najdete t&#x159;eba na <a href=\"https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt\">CC0</a>.</p>\n<p>Pokud ale se souborem <code>LICENSE</code> vytvo&#x159;&#xED;te zdrojov&#xFD; bal&#xED;&#x10D;ek, soubor v archivu\nnebude. Je to proto, &#x17E;e se standardn&#x11B; do archivu p&#x159;id&#xE1;vaj&#xED; jen n&#x11B;kter&#xE9; soubory.\nDal&#x161;&#xED; soubory lze p&#x159;idat pomoc&#xED; souboru <code>MANIFEST.in</code>, dle <a href=\"https://docs.python.org/3/distutils/sourcedist.html#specifying-the-files-to-distribute\">dokumentace</a>.</p>\n<p>V na&#x161;em p&#x159;&#xED;pad&#x11B; bude <code>MANIFEST.in</code> vypadat takto:</p>\n<div class=\"highlight\"><pre><code>include LICENSE</code></pre></div><p>P&#x159;i dal&#x161;&#xED;m spu&#x161;t&#x11B;n&#xED; u&#x17E; <code>setup.py</code> p&#x159;id&#xE1; i soubor <code>LICENSE</code>.\nTo m&#x16F;&#x17E;ete zkontrolovat i ve v&#xFD;sledn&#xE9;m archivu.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python setup.py sdist\n<span class=\"go\">...</span>\n<span class=\"go\">hard linking LICENSE -&gt; isholiday-0.1</span>\n<span class=\"go\">hard linking MANIFEST.in -&gt; isholiday-0.1</span>\n<span class=\"go\">hard linking README -&gt; isholiday-0.1</span>\n<span class=\"go\">...</span>\n</pre></div><p>Hotov&#xFD; bal&#xED;&#x10D;ek pak m&#x16F;&#x17E;ete nainstalovat pomoc&#xED; n&#xE1;stroje <code>pip</code>.\nDoporu&#x10D;uji to d&#x11B;lat v jin&#xE9;m virtu&#xE1;ln&#xED;m prost&#x159;ed&#xED; &#x2013; v aktu&#xE1;ln&#xED;m u&#x17E; ho m&#xE1;te\nnainstalovan&#xFD;.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\"># </span>v jin&#xE9; konzoli, v jin&#xE9;m adres&#xE1;&#x159;i\n<span class=\"gp\">$ </span>python3 -m venv __venv2__\n<span class=\"gp\">$ </span>. __venv2__/bin/activate\n<span class=\"gp\">(__venv2__)$ </span>python -m pip install cesta/k/projektu/dist/isholiday-0.1.tar.gz\n<span class=\"go\">Processing cesta/k/projektu/dist/isholiday-0.1.tar.gz</span>\n<span class=\"go\">Installing collected packages: isholiday</span>\n<span class=\"go\">  Running setup.py install for isholiday ... done</span>\n<span class=\"go\">Successfully installed isholiday-0.1</span>\n</pre></div><h2>V&#xED;ce argument&#x16F; pro setup()</h2>\n<p>Na chv&#xED;li se vr&#xE1;t&#xED;me k vol&#xE1;n&#xED; funkce <code>setup()</code> a p&#x159;id&#xE1;me co nejv&#xED;c dal&#x161;&#xED;ch\npolo&#x17E;ek.\nJejich vysv&#x11B;tlen&#xED; najdete <a href=\"https://packaging.python.org/distributing/#setup-args\">v dokumentaci</a>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">setuptools</span> <span class=\"kn\">import</span> <span class=\"n\">setup</span>\n\n\n<span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s1\">&apos;README&apos;</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n    <span class=\"n\">long_description</span> <span class=\"o\">=</span> <span class=\"s1\">&apos;&apos;</span><span class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"o\">.</span><span class=\"n\">readlines</span><span class=\"p\">())</span>\n\n\n<span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&apos;isholiday&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">version</span><span class=\"o\">=</span><span class=\"s1\">&apos;0.1&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">description</span><span class=\"o\">=</span><span class=\"s1\">&apos;Finds Czech holiday for given year&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">long_description</span><span class=\"o\">=</span><span class=\"n\">long_description</span><span class=\"p\">,</span>\n    <span class=\"n\">author</span><span class=\"o\">=</span><span class=\"s1\">&apos;Ond&#x159;ej Caletka&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">author_email</span><span class=\"o\">=</span><span class=\"s1\">&apos;ondrej@caletka.cz&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">keywords</span><span class=\"o\">=</span><span class=\"s1\">&apos;holiday,dates&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">license</span><span class=\"o\">=</span><span class=\"s1\">&apos;Public Domain&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">url</span><span class=\"o\">=</span><span class=\"s1\">&apos;https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">py_modules</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s1\">&apos;isholiday&apos;</span><span class=\"p\">],</span>\n    <span class=\"n\">classifiers</span><span class=\"o\">=</span><span class=\"p\">[</span>\n        <span class=\"s1\">&apos;Intended Audience :: Developers&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;License :: Public Domain&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;Operating System :: POSIX :: Linux&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;Programming Language :: Python&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;Programming Language :: Python :: Implementation :: CPython&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;Programming Language :: Python :: 3&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;Programming Language :: Python :: 3.5&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;Topic :: Software Development :: Libraries&apos;</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n    <span class=\"n\">zip_safe</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n</pre></div><p>V&#x161;imn&#x11B;te si n&#x11B;kolika v&#x11B;c&#xED;. V prvn&#xED; &#x159;ad&#x11B; v <code>long_description</code> vid&#xED;te, &#x17E;e jsme\npo&#x159;&#xE1;d je&#x161;t&#x11B; v Pythonu a m&#x16F;&#x17E;eme si u&#x161;et&#x159;it duplikaci n&#x11B;jak&#xFD;ch informac&#xED; pomoc&#xED;\nmal&#xE9;ho kousku k&#xF3;du. Dal&#x161;&#xED;m zaj&#xED;mav&#xFD;m argumentem je <code>classifiers</code>. Jsou to\nv podstat&#x11B; takov&#xE9; tagy nebo strukturovan&#xE9; informace o bal&#xED;&#x10D;ku.\nZ&#xE1;sadn&#x11B; si je nevym&#xFD;&#x161;l&#xED;me sami, ale hled&#xE1;me je v\n<a href=\"https://pypi.org/pypi?%3Aaction=list_classifiers\">seznamu</a>.\nTyto informace budou pozd&#x11B;ji vid&#x11B;t na <a href=\"https://pypi.org\">PyPI</a> a\np&#x16F;jde podle nich hledat.</p>\n<p>Argument <code>zip_safe=False</code> zajist&#xED;, &#x17E;e se modul nainstaluje do adres&#xE1;&#x159;e.\nSetuptools toti&#x17E; maj&#xED; nep&#x159;&#xED;jemn&#xFD; zlozvyk instalovat moduly jako <code>zip</code>,\nco&#x17E; komplikuje pr&#xE1;ci s datov&#xFD;mi soubory (nap&#x159;. <em>templates</em> pro Flask).\nJe proto lep&#x161;&#xED; <code>zip_safe=False</code> uv&#xE9;st.</p>\n<h2>V&#xED;ce soubor&#x16F; s Python k&#xF3;dem</h2>\n<p>Dote&#x10F; jsme vytv&#xE1;&#x159;eli bal&#xED;&#x10D;ek jen z jednoho zdrojov&#xE9;ho souboru <code>isholiday.py</code>.\nCo ale d&#x11B;lat, pokud je n&#xE1;&#x161; projekt v&#x11B;t&#x161;&#xED; a obsahuje soubor&#x16F; v&#xED;ce?\nTeoreticky je mo&#x17E;n&#xE9; je p&#x159;idat v&#x161;echny do <code>py_modules</code>, ale nen&#xED; to dobr&#xFD; n&#xE1;pad.</p>\n<div class=\"admonition note\"><p>Pro&#x10D; to vlastn&#x11B; nen&#xED; dobr&#xFD; n&#xE1;pad? Jednotliv&#xE9; moduly ze v&#x161;ech nainstalovan&#xFD;ch\nbal&#xED;&#x10D;k&#x16F; by byly rozesety bez ladu a skladu mezi ostatn&#xED;mi.\nMohl by snadno nastat konflikt v n&#xE1;zvech, nap&#x159;&#xED;klad pokud by v&#xED;ce bal&#xED;&#x10D;k&#x16F;\nm&#x11B;lo modul <code>utils</code>.\nSlu&#x161;n&#x11B; vychovan&#xFD; Pythonista d&#xE1; do ka&#x17E;d&#xE9;ho bal&#xED;&#x10D;ku pr&#xE1;v&#x11B; jeden modul,\npojmenovan&#xFD; stejn&#x11B; jako bal&#xED;&#x10D;ek.</p>\n</div><p>Rad&#x11B;ji ud&#x11B;l&#xE1;me modul ve form&#x11B; slo&#x17E;ky. V na&#x161;em p&#x159;&#xED;pad&#x11B; soubor\n<code>isholiday.py</code> zat&#xED;m p&#x159;esuneme do <code>isholiday/__init__.py</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>tree\n<span class=\"go\">.</span>\n<span class=\"go\">&#x251C;&#x2500;&#x2500; isholiday</span>\n<span class=\"go\">&#x2502;&#xA0;&#xA0; &#x2514;&#x2500;&#x2500; __init__.py</span>\n<span class=\"go\">&#x251C;&#x2500;&#x2500; LICENSE</span>\n<span class=\"go\">&#x251C;&#x2500;&#x2500; MANIFEST.in</span>\n<span class=\"go\">&#x251C;&#x2500;&#x2500; README</span>\n<span class=\"go\">&#x2514;&#x2500;&#x2500; setup.py</span>\n\n<span class=\"go\">1 directory, 5 files</span>\n</pre></div><p>Soubor <code>__init__.py</code> jednak zna&#x10D;&#xED;, &#x17E;e adres&#xE1;&#x159; <code>isholiday</code> je pythonn&#xED; modul,\na tak&#xE9; obsahuje k&#xF3;d, kter&#xFD; se spust&#xED; p&#x159;i importu modulu <code>isholiday</code>.</p>\n<p>Mus&#xED;me je&#x161;t&#x11B; m&#xED;rn&#x11B; upravit <code>setup.py</code> &#x2013; m&#xED;sto <code>py_modules</code> pou&#x17E;ijeme <code>packages</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gh\">diff --git a/setup.py b/setup.py</span>\n<span class=\"gh\">index 3a69792..6b453ab 100644</span>\n<span class=\"gd\">--- a/setup.py</span>\n<span class=\"gi\">+++ b/setup.py</span>\n<span class=\"gu\">@@ -11,7 +11,7 @@ setup(</span>\n     keywords=&apos;holiday,dates&apos;,\n     license=&apos;Public Domain&apos;,\n     url=&apos;https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e&apos;,\n<span class=\"gd\">-    py_modules=[&apos;isholiday&apos;],</span>\n<span class=\"gi\">+    packages=[&apos;isholiday&apos;],</span>\n     classifiers=[\n         &apos;Intended Audience :: Developers&apos;,\n         &apos;License :: Public Domain&apos;,\n</pre></div><p>P&#x159;&#xED;padn&#x11B;, co&#x17E; je je&#x161;t&#x11B; lep&#x161;&#xED;, m&#x16F;&#x17E;eme pou&#x17E;&#xED;t <code>find_packages()</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">setuptools</span> <span class=\"kn\">import</span> <span class=\"n\">setup</span><span class=\"p\">,</span> <span class=\"n\">find_packages</span>\n\n<span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"o\">...</span>\n    <span class=\"n\">packages</span><span class=\"o\">=</span><span class=\"n\">find_packages</span><span class=\"p\">(),</span>\n    <span class=\"o\">...</span>\n<span class=\"p\">)</span>\n</pre></div><div class=\"admonition note\"><p>A jak&#xFD; je tedy vlastn&#x11B; rozd&#xED;l mezi <code>py_modules</code> a <code>packages</code>?\nZjednodu&#x161;en&#x11B;: Ten prvn&#xED; je na soubory, ten druh&#xFD; na adres&#xE1;&#x159;e.</p>\n</div><p>Moment&#xE1;ln&#x11B; m&#xE1;me v&#x161;echen k&#xF3;d p&#x159;&#xED;mo v <code>__init__.py</code>, co&#x17E; sice funguje,\nale ide&#xE1;ln&#xED; to nen&#xED;. Dobr&#xE9; je m&#xED;t k&#xF3;d v samostatn&#xFD;ch souborech a v <code>__init__.py</code>\npouze importovat ve&#x159;ejn&#xE9; rozhran&#xED;, tedy to, co budou z va&#x161;eho modulu importovat\njeho u&#x17E;ivatel&#xE9;.</p>\n<p>V souboru <code>__init__.py</code> by tak prakticky &#x17E;&#xE1;dn&#xFD; k&#xF3;d krom&#x11B; import&#x16F; b&#xFD;t nem&#x11B;l.\nP&#x159;esu&#x148;te tedy obsah <code>__init__.py</code> do <code>holidays.py</code> a\ndo <code>__init__.py</code> m&#xED;sto toho napi&#x161;te:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">.holidays</span> <span class=\"kn\">import</span> <span class=\"n\">getholidays</span><span class=\"p\">,</span> <span class=\"n\">isholiday</span>\n\n<span class=\"n\">__all__</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s1\">&apos;getholidays&apos;</span><span class=\"p\">,</span> <span class=\"s1\">&apos;isholiday&apos;</span><span class=\"p\">]</span>\n</pre></div><p>Te&#x10D;ka v p&#x159;&#xED;kazu <code>import</code> nen&#xED; chyba: je to zkratka pro aktu&#xE1;ln&#xED; modul.\nM&#x16F;&#x17E;eme ps&#xE1;t i <code>from isholiday.holidays import ...</code>,\nco&#x17E; ale trochu zt&#x11B;&#x17E;uje p&#x159;&#xED;padn&#xE9; p&#x159;ejmenov&#xE1;n&#xED; modulu.</p>\n<p>Ono <code>__all__</code> pak explicitn&#x11B; definuje rozhran&#xED; modulu. Nap&#x159;&#xED;klad s p&#x16F;vodn&#xED;m\nmodulem &#x161;lo prov&#xE9;st <code>from isholiday import datetime</code>, ale asi by nikdo\nne&#x10D;ekal, &#x17E;e tahle mo&#x17E;nost bude nutn&#x11B; zachov&#xE1;na i v p&#x159;&#xED;&#x161;t&#xED;ch verz&#xED;ch knihovny.\nSeznamem <code>__all__</code> d&#xE1;te najevo, &#x17E;e tyhle funkce nejsou jen &#x201E;n&#xE1;hodn&#xE9; importy&#x201C;,\na z&#xE1;rove&#x148; t&#xED;m zamez&#xED;te r&#x16F;zn&#xFD;m varov&#xE1;n&#xED;m o\nimportovan&#xE9;m ale nevyu&#x17E;it&#xE9;m modulu, kter&#xE9; m&#x16F;&#x17E;e hl&#xE1;sit va&#x161;e IDE nebo linter.</p>\n<div class=\"admonition note\"><p>Python samotn&#xFD; pak <code>__all__</code> pou&#x17E;&#xED;v&#xE1; jako seznam prom&#x11B;nn&#xFD;ch importovan&#xFD;ch\np&#x159;es <code>from isholiday import *</code> Tento zp&#x16F;sob importu nevid&#xED;me r&#xE1;di,\nproto&#x17E;e znep&#x159;ehled&#x148;uje k&#xF3;d, to ale neznamen&#xE1;, &#x17E;e to mus&#xED;me u&#x17E;ivatel&#x16F;m\nna&#x161;&#xED; knihovny znep&#x159;&#xED;jem&#x148;ovat (nap&#x159;. pro interaktivn&#xED; re&#x17E;im).</p>\n</div><h2>Spou&#x161;t&#x11B;n&#xED; bal&#xED;&#x10D;ku</h2>\n<p>Pokus&#xED;me-li se te&#x10F; program spustit pomoc&#xED; <code>python -m isholiday</code>,\nnaraz&#xED;me na probl&#xE9;m: na rozd&#xED;l od souboru se slo&#x17E;ka s k&#xF3;dem takto spustit ned&#xE1;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m isholiday\n<span class=\"go\">python: No module named isholiday.__main__; &apos;isholiday&apos; is a package and cannot be directly executed</span>\n</pre></div><p>Nam&#xED;sto spu&#x161;t&#x11B;n&#xED; souboru (typicky s blokem <code>if __name__ == &apos;__main__&apos;:</code>) toti&#x17E;\nPython v tomto p&#x159;&#xED;pad&#x11B; hled&#xE1; <em>soubor</em> pojmenovan&#xFD; <code>__main__.py</code> a spust&#xED; ten.</p>\n<p>Soubor <code>__main__.py</code> nen&#xED; ur&#x10D;en&#xFD; k tomu, aby se z n&#x11B;ho importovalo, proto\nby m&#x11B;l obsahovat co nejm&#xE9;n&#x11B; k&#xF3;du &#x2013; ide&#xE1;ln&#x11B; jen vol&#xE1;n&#xED; funkce, kter&#xE1; je\ndefinovan&#xE1; jinde. Vytvo&#x159;te proto <code>__main__.py</code> s n&#xE1;sleduj&#xED;c&#xED;m obsahem:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">.holidays</span> <span class=\"kn\">import</span> <span class=\"n\">main</span>\n\n<span class=\"n\">main</span><span class=\"p\">()</span>\n</pre></div><p>a v <code>holidays.py</code> zam&#x11B;&#x148;te <code>if __name__ == &apos;__main__&apos;:</code> za <code>def main():</code>.</p>\n<p>Skript te&#x10F; bude mo&#x17E;n&#xE9; pou&#x17E;&#xED;t pomoc&#xED; <code>python -m isholiday</code>.\nBude to fungovat i tehdy, kdy&#x17E; vytvo&#x159;&#xED;te bal&#xED;&#x10D;ek (<code>python setup.py sdist</code>)\na nainstalujete ho v jin&#xE9;m virtu&#xE1;ln&#xED;m prost&#x159;ed&#xED;.</p>\n<h2>Programy pro p&#x159;&#xED;kazovou &#x159;&#xE1;dku</h2>\n<p>Pokud chcete, aby v&#xE1;&#x161; modul umo&#x17E;&#x148;oval spou&#x161;t&#x11B;n&#xED; p&#x159;&#xED;mo z p&#x159;&#xED;kazov&#xE9; &#x159;&#xE1;dky,\nbez <code>python -m</code>, m&#x11B;li byste pou&#x17E;&#xED;t <a href=\"https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins\">entrypoints</a>.\nK tomu je pot&#x159;eba p&#x159;idat do vol&#xE1;n&#xED; <code>setup</code> v <code>setup.py</code> p&#x159;&#xED;slu&#x161;n&#xFD; argument:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"o\">...</span>\n    <span class=\"n\">entry_points</span><span class=\"o\">=</span><span class=\"p\">{</span>\n        <span class=\"s1\">&apos;console_scripts&apos;</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n            <span class=\"s1\">&apos;isholiday_demo = isholiday.holidays:main&apos;</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n    <span class=\"p\">},</span>\n<span class=\"p\">)</span>\n</pre></div><p><code>isholiday_demo</code> je jm&#xE9;no <em>entrypointu</em>, tedy p&#x159;&#xED;kazu pro p&#x159;&#xED;kazovou &#x159;&#xE1;dku.\n<code>isholiday.holidays:main</code> je pak cesta k funkci ve tvaru <code>modul:funkce</code>;\nfunkce m&#x16F;&#x17E;e b&#xFD;t v modulu definovan&#xE1; nebo importovan&#xE1;.</p>\n<p>Skript bude mo&#x17E;n&#xE9; pou&#x17E;&#xED;t, je-li aktivn&#xED; prost&#x159;ed&#xED;, kde je nainstalov&#xE1;n, jen\nzad&#xE1;n&#xED;m jm&#xE9;na <em>entrypointu</em>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python setup.py sdist\n</pre></div><div class=\"highlight\"><pre><span></span><span class=\"gp\"># </span>v jin&#xE9; konzoli, v jin&#xE9;m virtu&#xE1;ln&#xED;m prost&#x159;ed&#xED;\n<span class=\"gp\">(__venv2__)$ </span>python -m pip install --upgrade cesta/k/projektu/dist/isholiday-0.1.tar.gz\n<span class=\"gp\">(__venv2__)$ </span>isholiday_demo\n<span class=\"go\">...</span>\n<span class=\"go\">Mon Mar 28 00:00:00 2016 True</span>\n<span class=\"go\">Tue Mar 28 00:00:00 2017 False</span>\n<span class=\"go\">Fri Apr 14 00:00:00 2017 True</span>\n</pre></div><h2>Specifikace z&#xE1;vislost&#xED;</h2>\n<p>Bal&#xED;&#x10D;ky na PyPI mohou z&#xE1;viset na dal&#x161;&#xED;ch bal&#xED;&#x10D;k&#xE1;ch. V p&#x159;&#xED;pad&#x11B; <code>isholiday</code> to\npot&#x159;eba nen&#xED;, ale v &#xFA;loh&#xE1;ch z minul&#xFD;ch cvi&#x10D;en&#xED; ano.</p>\n<p>Existuje n&#x11B;kolik &#xFA;rovn&#xED; z&#xE1;vislost&#xED;, ve v&#x11B;t&#x161;in&#x11B; p&#x159;&#xED;pad&#x16F; si\nvysta&#x10D;&#xED;te s argumentem <code>install_requires</code>.\nBal&#xED;&#x10D;ek, kter&#xFD; z&#xE1;vis&#xED; na knihovn&#xE1;ch <code>Flask</code> (jak&#xE9;koli verze) a\n<code>click</code> (verze 6 a vy&#x161;&#x161;&#xED;) by v <code>setup.py</code> m&#x11B;l m&#xED;t:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"o\">...</span>\n    <span class=\"n\">install_requires</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s1\">&apos;Flask&apos;</span><span class=\"p\">,</span> <span class=\"s1\">&apos;click&gt;=6&apos;</span><span class=\"p\">],</span>\n<span class=\"p\">)</span>\n</pre></div><h3>Soubor requirements.txt</h3>\n<p>Krom&#x11B; z&#xE1;vislost&#xED; v <code>setup.py</code> se u pythonn&#xED;ch projekt&#x16F; &#x10D;asto setk&#xE1;me se souborem\n<code>requirements.txt</code>, kter&#xFD; obsahuje p&#x159;esn&#xE9; verze v&#x161;ech z&#xE1;vislost&#xED;, v&#x10D;etn&#x11B;\ntranzitivn&#xED;ch &#x2013; t.j. z&#xE1;vis&#xED;-li n&#xE1;&#x161; bal&#xED;&#x10D;ek na <code>Flask</code> a <code>Flask</code> na <code>Jinja2</code>,\nnajdeme v <code>requirements.txt</code> mimojin&#xE9; &#x159;&#xE1;dky:</p>\n<div class=\"highlight\"><pre><code>Flask==0.11.1\nJinja2==2.8</code></pre></div><p>Tento soubor se pou&#x17E;&#xED;v&#xE1;, kdy&#x17E; je pot&#x159;eba p&#x159;esn&#x11B; replikovat prost&#x159;ed&#xED;, kde\nprogram b&#x11B;&#x17E;&#xED;, nap&#x159;&#xED;klad mezi testovac&#xED;m strojem a produk&#x10D;n&#xED;m nasazen&#xED;m\nwebov&#xE9; aplikace.\nTento soubor se d&#xE1; vygenerovat z aktu&#xE1;ln&#xED;ho prost&#x159;ed&#xED; zad&#xE1;n&#xED;m\n<code>python -m pip freeze &gt; requirements.txt</code> a bal&#xED;&#x10D;ky v n&#x11B;m se daj&#xED; nainstalovat\npomoc&#xED; <code>python -m pip install -r requirements.txt</code>.\nMy ho pou&#x17E;&#xED;vat nebudeme, vysta&#x10D;&#xED;me si s voln&#x11B;j&#x161;&#xED; specifikac&#xED; z&#xE1;vislost&#xED;\nv <code>setup.py</code>.</p>\n<h2>Upload na PyPI</h2>\n<p>Bal&#xED;&#x10D;ek jde zaregistrovat a nahr&#xE1;t na PyPI. P&#x16F;vodn&#x11B; k tomu slou&#x17E;ily p&#x159;&#xED;kazy\n<code>setup.py</code> <code>register</code> a <code>upload</code>, ale tyto p&#x159;&#xED;kazy pou&#x17E;&#xED;vaj&#xED; HTTP, co&#x17E; nen&#xED;\nbezpe&#x10D;n&#xE9;. Prototo je lep&#x161;&#xED; pou&#x17E;&#xED;t program <code>twine</code> (instalovateln&#xFD; p&#x159;es pip),\nkter&#xFD; pou&#x17E;&#xED;v&#xE1; HTTPS.</p>\n<p>Budete si pot&#x159;ebovat za&#x159;&#xED;dit\n<a href=\"https://pypi.org/account/register/\">&#xFA;&#x10D;et na PyPI</a>,\n<a href=\"https://test.pypi.org/account/register/\">&#xFA;&#x10D;et na testovac&#xED; PyPI</a>\na vytvo&#x159;it konfigura&#x10D;n&#xED; soubor <code>~/.pypirc</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">[distutils]</span>\n<span class=\"na\">index-servers</span><span class=\"o\">=</span><span class=\"s\"></span>\n<span class=\"s\">    pypi</span>\n<span class=\"s\">    testpypi</span>\n\n<span class=\"k\">[pypi]</span>\n<span class=\"na\">username</span> <span class=\"o\">=</span> <span class=\"s\">&lt;your user name goes here&gt;</span>\n<span class=\"na\">password</span> <span class=\"o\">=</span> <span class=\"s\">&lt;your password goes here&gt;</span>\n\n<span class=\"k\">[testpypi]</span>\n<span class=\"na\">repository</span> <span class=\"o\">=</span> <span class=\"s\">https://test.pypi.org/legacy/</span>\n<span class=\"na\">username</span> <span class=\"o\">=</span> <span class=\"s\">&lt;your user name goes here&gt;</span>\n<span class=\"na\">password</span> <span class=\"o\">=</span> <span class=\"s\">&lt;your password goes here&gt;</span>\n</pre></div><p>Hesla m&#x16F;&#x17E;ete vynechat, pokud je budete cht&#xED;t poka&#x17E;d&#xE9; zad&#xE1;vat.</p>\n<p>Pou&#x17E;&#xED;v&#xE1;te-li Windows, je pot&#x159;eba nastavit prom&#x11B;nnou prost&#x159;ed&#xED; <code>HOME</code> na adres&#xE1;&#x159;\nse souborem <code>.pypirc</code>, nap&#x159;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt; </span>set HOME=C:\\cesta\\k\\nastaveni\n</pre></div><p>Registrace projektu a nahr&#xE1;n&#xED; na testovac&#xED; PyPI se prov&#xE1;d&#xED; pomoc&#xED; p&#x159;&#xED;kazu\n<code>upload</code>: ten projekt zaregistrueje (pokud to jde) a nahraje samotn&#xFD; bal&#xED;&#x10D;ek:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>twine upload -r testpypi dist/isholiday-0.1.tar.gz\n<span class=\"go\">Uploading distributions to https://test.pypi.org/legacy/</span>\n<span class=\"go\">Uploading isholiday-0.1.tar.gz</span>\n<span class=\"go\">[================================] 8379/8379 - 00:00:02</span>\n</pre></div><p>Prvn&#xED; nahr&#xE1;n&#xED; se zda&#x159;&#xED;, jen pokud jm&#xE9;no projektu ji&#x17E; nen&#xED; zabran&#xE9;.\nDal&#x161;&#xED; nahr&#xE1;v&#xE1;n&#xED; je povoleno jen v&#xE1;m, p&#x159;&#xED;padn&#x11B; u&#x17E;ivatel&#x16F;m,\nkter&#xFD;m to povl&#xED;te p&#x159;es webov&#xE9; rozhran&#xED;.\nPo &#xFA;sp&#x11B;&#x161;n&#xE9;m nahr&#xE1;n&#xED; lze nahr&#xE1;vat dal&#x161;&#xED; verze modulu, ale mus&#xED; b&#xFD;t nov&#x11B;j&#x161;&#xED;\nne&#x17E; ta, co u&#x17E; na PyPI je. Nejde tedy jednou nahran&#xFD; modul p&#x159;epsat.</p>\n<p>Sv&#x16F;j bal&#xED;&#x10D;ek najdete na <code>https://test.pypi.org/project/&lt;n&#xE1;zev_bal&#xED;&#x10D;ku&gt;/</code>.</p>\n<p>Pro nahr&#xE1;n&#xED; na opravdovou PyPI sta&#x10D;&#xED; vynechat <code>-r testpypi</code>.\nZab&#xED;rat jm&#xE9;na na opravdov&#xE9; PyPI jen tak nen&#xED; hezk&#xE9; v&#x16F;&#x10D;i ostatn&#xED;m Pythonist&#x16F;m;\nregistrujte tedy pros&#xED;m jen moduly, kter&#xE9; budou n&#x11B;jak pro ostatn&#xED; u&#x17E;ite&#x10D;n&#xE9;.</p>\n<h2>Instalace pomoc&#xED; pip</h2>\n<p>Projekt nahran&#xFD; na PyPI by m&#x11B;lo j&#xED;t nainstalovat pomoc&#xED; pipu.\nV p&#x159;&#xED;pad&#x11B; pou&#x17E;it&#xED; ostr&#xE9; verze PyPI sta&#x10D;&#xED; k instalaci zadat n&#xE1;zev bal&#xED;&#x10D;ku:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python -m pip install &lt;n&#xE1;zev_bal&#xED;&#x10D;ku&gt;\n</pre></div><p>Pokud v&#x161;ak pou&#x17E;ijeme testovac&#xED; PyPI, je nutn&#xE9; pipu &#x159;&#xED;ct, aby bal&#xED;&#x10D;ek hledal tam.\n<a href=\"https://wiki.python.org/moin/TestPyPI\">Postup</a> uveden&#xFD; v dokumentaci nen&#xED;\nv tomto p&#x159;&#xED;pad&#x11B; nejvhodn&#x11B;j&#x161;&#xED;, proto&#x17E;e z testovac&#xED; PyPI vezme jak n&#xE1;&#x161; bal&#xED;&#x10D;ek,\ntak i p&#x159;&#xED;padn&#xE9; z&#xE1;vislosti, kter&#xE9; mohou b&#xFD;t zastaral&#xE9;, rozbit&#xE9; &#x10D;i jinak &#x161;kodliv&#xE9;.</p>\n<p>Lep&#x161;&#xED; by bylo, kdyby pip nainstaloval z&#xE1;vislosti z ostr&#xE9; PyPI a na testovac&#xED;\nhledal jen n&#xE1;&#x161; projekt. Toho se d&#xE1; doc&#xED;lit p&#x159;ep&#xED;na&#x10D;em <code>--extra-index-url</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python -m pip install --extra-index-url https://test.pypi.org/pypi &lt;n&#xE1;zev_bal&#xED;&#x10D;ku&gt;\n</pre></div><p>V tomto p&#x159;&#xED;pad&#x11B; pip nejd&#x159;&#xED;v prohled&#xE1; ostrou PyPI, a pokud nenajde po&#x17E;adovan&#xFD;\nbal&#xED;&#x10D;ek, pou&#x17E;ije testovac&#xED; PyPI. Zde je pot&#x159;eba d&#xE1;vat pozor na n&#xE1;zev projektu,\nproto&#x17E;e p&#x159;&#xED;padn&#xE9; konflikty mezi ostrou a testovac&#xED; PyPI se nekontroluj&#xED;.\nPokud tedy m&#xE1;me projekt na testovac&#xED; PyPI a na ostr&#xE9; existuje projekt se\nstejn&#xFD;m n&#xE1;zvem, nainstaluje se ten z ostr&#xE9; verze.</p>\n<p>V p&#x159;&#xED;pad&#x11B;, &#x17E;e tento probl&#xE9;m nastane, je mo&#x17E;n&#xE9; ho &#x10D;&#xE1;ste&#x10D;n&#x11B; obej&#xED;t specifikac&#xED;\nverze instalovan&#xE9;ho bal&#xED;&#x10D;ku:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python -m pip install --extra-index-url https://test.pypi.org/pypi &lt;n&#xE1;zev_bal&#xED;&#x10D;ku&gt;<span class=\"o\">==</span><span class=\"m\">0</span>.3\n</pre></div><p>Pokud u duplicitn&#xED;ho projektu na ostr&#xE9; PyPI neexistuje po&#x17E;adovan&#xE1; verze,\nnainstaluje se n&#xE1;&#x161; projekt z testovac&#xED; PyPI.</p>\n<p>Jin&#xE1; mo&#x17E;nost je zadat p&#x159;&#xED;mo cestu k archivu s bal&#xED;&#x10D;kem m&#xED;sto jeho n&#xE1;zvu.\nZde pak na um&#xED;st&#x11B;n&#xED; bal&#xED;&#x10D;ku ani verzi nez&#xE1;le&#x17E;&#xED;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python -m pip install https://test-files.pythonhosted.org/packages/.../&lt;n&#xE1;zev_bal&#xED;&#x10D;ku&gt;-0.3.tar.gz\n</pre></div><p>Archiv se d&#xE1; naj&#xED;t na informa&#x10D;n&#xED; str&#xE1;nce o na&#x161;em projektu na PyPI.</p>\n<h2>Datov&#xE9; soubory</h2>\n<p>N&#x11B;kter&#xE9; bal&#xED;&#x10D;ky krom&#x11B; samotn&#xE9;ho k&#xF3;du pot&#x159;ebuj&#xED; i datov&#xE9; soubory.\nNap&#x159;&#xED;klad aplikace ve Flasku pot&#x159;ebuj&#xED; <em>templates</em>.\nTakov&#xE1; data se daj&#xED; p&#x159;idat parametrem <code>package_data</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">setup</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">,</span>\n    <span class=\"n\">packages</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s1\">&apos;hello_flask&apos;</span><span class=\"p\">],</span>\n    <span class=\"o\">...</span>\n    <span class=\"n\">package_data</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"s1\">&apos;hello_flask&apos;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"s1\">&apos;templates/*.html&apos;</span><span class=\"p\">]},</span>\n<span class=\"p\">)</span>\n</pre></div><p>Dal&#x161;&#xED; informace jsou odk&#xE1;zan&#xE9; v <a href=\"https://packaging.python.org/distributing/#package-data\">dokumentaci</a>.</p>\n<h2>Wheel</h2>\n<p>Zat&#xED;m jsme se zab&#xFD;vali jen zdrojov&#xFD;mi bal&#xED;&#x10D;ky <code>sdist</code> (<em>source distribution</em>).\nExistuj&#xED; ale i bal&#xED;&#x10D;ky &#x201E;zkompilovan&#xE9;&#x201D; &#x2013; <code>bdist</code> (<em>binary distribution</em>).\nKdy&#x17E; se instaluje zdrojov&#xFD; bal&#xED;&#x10D;ek, vykon&#xE1;v&#xE1; se k&#xF3;d ze souboru <code>setup.py</code>.\nBin&#xE1;rn&#xED; bal&#xED;&#x10D;ek se m&#xED;sto toho jen rozbal&#xED; na pat&#x159;i&#x10D;n&#xE9; m&#xED;sto.</p>\n<p>Z historick&#xFD;ch d&#x16F;vod&#x16F; existuje n&#x11B;kolik r&#x16F;zn&#xFD;ch druh&#x16F; bin&#xE1;rn&#xED;ch distribuc&#xED;,\nv&#xA0;sou&#x10D;asn&#xE9; dob&#x11B; je ale d&#x16F;le&#x17E;it&#xE1; pouze mo&#x17E;nost <code>bdist_wheel</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>python setup.py bdist_wheel\n</pre></div><p>V&#xFD;sledek je v souboru <code>dist/*.whl</code>.</p>\n<div class=\"admonition note\"><p>Pokud v&#xE1;m p&#x159;&#xED;kaz nefunguje, nainstalujte bal&#xED;k <code>wheel</code>.</p>\n</div><p>Obsah wheelu m&#x16F;&#x17E;ete prozkoumat, je to oby&#x10D;ejn&#xFD; ZIP.</p>\n<p>Na&#x161;e programy jsou zat&#xED;m platformn&#x11B; nez&#xE1;visl&#xE9; a ve wheelu,\ni kdy&#x17E; se jmenuje bin&#xE1;rn&#xED;, &#x17E;&#xE1;dn&#xE9; bin&#xE1;rn&#xED; soubory nejsou.\nTo se ale zm&#x11B;n&#xED;, a&#x17E; se budeme zab&#xFD;vat tvorbou modul&#x16F; v jazyce C:\n<code>sdist</code> pak obsahuje zdrojov&#xE9; soubory a <code>bdist_wheel</code> zkompilovan&#xE9; moduly.</p>\n<p>Potom je dobr&#xE9; distribuovat oba dva &#x2013; ka&#x17E;d&#xFD; m&#xE1; sv&#xE9; v&#xFD;hody:</p>\n<ul>\n<li><em>sdist</em> jde nainstalovat na r&#x16F;zn&#xFD;ch opera&#x10D;n&#xED;ch syst&#xE9;mech i procesorov&#xFD;ch\narchitektur&#xE1;ch,</li>\n<li><em>sdist</em> tradi&#x10D;n&#x11B; obsahuje soubory jako LICENSE a README, ale</li>\n<li><em>wheel</em> p&#x159;i instalaci nepot&#x159;ebuje nap&#x159;. p&#x159;eklada&#x10D;e C (v&#x161;echno u&#x17E; je p&#x159;elo&#x17E;en&#xE9;\npro konkr&#xE9;tn&#xED; OS a architekturu), a</li>\n<li><em>wheel</em> se rychleji instaluje.</li>\n</ul>\n<p>Proces vyd&#xE1;n&#xED; slo&#x17E;it&#x11B;j&#x161;&#xED;ho softwaru pak m&#x16F;&#x17E;e vypadat takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__)$ </span>rm dist/*\n<span class=\"gp\">(__venv__)$ </span>python setup.py sdist bdist_wheel\n<span class=\"go\">[... kontrola vytvo&#x159;en&#xFD;ch bal&#xED;&#x10D;k&#x16F; v &#x201E;&#x10D;ist&#xE9;m&#x201C; virtualenvu ...]</span>\n<span class=\"gp\">(__venv__)$ </span>python -m twine upload dist/*\n</pre></div><h2>Dal&#x161;&#xED;</h2>\n<p>K bal&#xED;&#x10D;kov&#xE1;n&#xED; existuje <a href=\"https://packaging.python.org/\">obs&#xE1;hl&#xE1; dokumentace</a>.\nBudete-li cht&#xED;t d&#x11B;lat n&#x11B;co, co v tomto kurzu nen&#xED;, pod&#xED;vejte se tam!</p>\n\n\n        "
    }
  }
}