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