Cvičení: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14.
Poslední aktuality jsou v issue #140 (z 25. dubna).
Na tomhle cvičení začneme krátkým seznámením se s tzv. port forwardingem přes SSH.
Ale hlavní náplní bude vývoj projektů v jazyce Python ve virtuálním (izolovaném) prostředí, které lze snadno distribuovat mezi jednotlivé vývojáře ve velkém softwarovém týmu. A také se podíváme, jak se programy chystají na další distribuci.
Všimněte si, že znalost vývoje Pythonu v izolovaném prostředí se bude hodit pro poslední domácí úkol byť AI vám tam může hodně pomoci.
Předstartovní kontrola
- Víte, co je SSH.
- Víte, co jsou TCP porty.
- Víte, jak se vytvářejí a jak jsou organizovány moduly v Pythonu.
- Jste připraveni na písemku z shellu.
Přesměrování portů přes SSH (SSH port forwarding)
Obecně platí, že služby poskytované počítačem by neměly být vystaveny na síti, aby si s nimi mohli hrát náhodní “bezpečnostní výzkumníci”. Proto je brána firewall obvykle nakonfigurována tak, aby kontrolovala přístup k počítači ze sítě.
Pokud má být služba poskytována pouze lokálně, je ještě jednodušší nechat ji poslouchat pouze na zařízení zpětné smyčky (loopback). Tímto způsobem k ní mohou přistupovat pouze místní uživatelé (včetně uživatelů připojených k počítači přes SSH).
Pro příklad si můžete povšimnout, že na linux.ms.mff.cuni.cz
je na portu
8080 naslouchající webový server. Tento webový server není dostupný, pokud
se k němu pokusíte přistupovat přes linux.ms.mff.cuni.cz
, ale lokální
přístup k němu (při přihlášení k linux.ms.mff.cuni.cz
) funguje.
you@laptop$ curl http://linux.ms.mff.cuni.cz:8080 # Fails
you@laptop$ ssh LOGIN@linux.ms.mff.cuni.cz curl --silent http://localhost:8080 # Works
Přístup k tomuto webovému serveru pomocí cURL je sice možný, ale není to uživatelsky nejpřívětivější způsob prohlížení webových stránek.
Přesměrování portů přes SSH (SSH local port forwarding)
SSH lze použít k vytvoření bezpečného tunelu, přes který je lokální port přesměrován na port přístupný ze vzdáleného stroje. V podstatě se připojíte ke zpětné smyčce na svém počítači a SSH tuto komunikaci přesměruje na vzdálený server, čímž efektivně zpřístupní vzdálený port.
Následující příkaz způsobí, že se lokální port 8888 bude chovat jako port
8080 na vzdáleném počítači. Část 127.0.0.1
odkazuje na zpětnou smyčku na
vzdáleném serveru (můžete tam napsat i localhost
)
ssh -L 8888:127.0.0.1:8080 -N linux.ms.mff.cuni.cz
Vždy nejprve uvedete, který lokální port chcete přesměrovat (8888), a poté cíl, jako byste se připojovali ze vzdáleného počítače (172.0.0.1:8080).
Pomocí -N
je toto připojení použitelné pouze pro přesměrování – pro jeho
ukončení použijte Ctrl-C (bez něj se přihlásíte i ke vzdálenému počítači).
Otevřete http://localhost:8888 v prohlížeči a zkontrolujte, zda vidíte
stejný obsah jako při použití výše uvedeného příkazu ssh LOGIN@linux.ms.mff.cuni.cz curl http://localhost:8080
.
Reverzní port forward (SSH remote/reverse port forwarding)
SSH umožňuje vytvořit také tzv. reverzní port forward.
V podstatě umožňuje otevřít spojení ze vzdáleného serveru k místnímu počítači (proti směru původního SSH spojení).
Prakticky můžete nastavit reverzní přesměrování portů například připojením se ze stolního počítače, který máte doma, na počítač v IMPAKTu/Rotundě a poté se pomocí tohoto reverzního přesměrování připojit z IMPAKTu/Rotundy zpět na svůj domácí počítač.
Tato vlastnost bude fungovat i v případě, že je váš počítač za NAT, což znemožňuje přímé připojení zvenčí.
Následující příkaz nastaví reverzní přesměrování portů tak, že připojení k
portu 2222
na vzdáleném počítači bude převedeno na připojení k portu 22
(ssh) na lokálním počítači:
ssh -N -R 2222:127.0.0.1:22 u-plN.ms.mff.cuni.cz
Nejprve uvedete vzdálený port k přesměrování (2222) a poté cíl, jako byste se připojovali z lokálního stroje (127.0.0.1:22).
Při zkoušení tohoto postupu se ujistěte, že je spuštěn démon sshd
(vzpomeňte si na lab 08 a příkaz systemctl
), a použijte jiný port než
2222, abyste zabránili kolizím.
Abyste se mohli připojit k vašemu počítači přes tento přesměrovaný port, musíte tak učinit z laboratoře IMPAKTu/Rotundy pomocí následujícího příkazu.
ssh -p 2222 your-desktop-login@localhost
Používáme localhost
, protože připojení je vázáno pouze na rozhraní
loopback, nikoli na skutečný síťový adaptér dostupný na laboratorních
počítačích. (Ve skutečnosti ssh
umožňuje navázat port forward na veřejnou
IP adresu, ale správce to často z bezpečnostních důvodů zakazuje.)
Vývoj softwaru ve virtuálním prostředí
Během předchozích cvičení jsme viděli, že doporučovaným způsobem instalace aplikací (a knihoven nebo datových souborů) na Linuxu je skrze správce balíčků. Ten nainstaluje aplikaci pro všechny uživatele, umožní upgrade celého systému a celkově udržuje systém v rozumném stavu.
Nicméně, ne vždy se systémová instalace hodí. Typický příklad jsou závislosti pro konkrétní projekt. Ty se obvykle neinstalují do systému a to hlavně z následujících důvodů:
- Potřebujete různé verze závislostí pro různé projekty.
- Nechcete si pamatovat, co všechno máte odinstalovat, když na projektu přestanete pracovat.
- Potřebujete určit, kdy dojde k jejich upgradu: upgrade OS by neměl ovlivnit váš projekt.
- Verze, které potřebujete, jsou jiné, než které zná správce balíčků.
- A nebo nejsou vůbec ve správci balíčků dostupné.
Z výše uvedených důvodů je mnohem lepší vytvořit instalaci určenou pro jeden
projekt, která je lépe oddělena (izolována) od zbytku systému.
V tomto ohledu instalace pro uživatele (tj. někam do $HOME
) nemusí
poskytnout takovou míru odstínění, jakou chceme.
Tento přístup podporuje většina rozumných programovacích jazyků a obvykle je najdeme pod názvy jako virtual environment, local repository, sandbox nebo něco podobného (koncepty nejsou 1:1 ve všech jazycích a nástrojích, ale základní myšlenka je stejná).
S virtuálním prostředím (virtual environment) jsou závislosti obvykle instalovány do určeného adresáře ve vašem projektu. Tento adresář potom neverzujeme (Gitem). Překladač/interpret je pak nasměrován na tento adresář.
Instalace do jednoho adresáře nezanese váš systém. Umožní vám pracovat na více projektech s nekompatibilními závislostmi, protože jsou kompletně oddělené.
Každý vývojář si pak může prostředí vytvořit, aniž by do repozitáře přibývaly soubory, které jsou specifické pro danou distribuci nebo operační systém. Ale konfigurační soubor zajistí, že všichni budou pracovat s identických prostředím (tj. stejné verze všech závislostí).
Také to znamená, že noví členové softwarového týmu si mohou prostředí jednoduše připravit právě s pomocí takového konfiguračního souboru.
Instalace závislostí
Uvnitř virtuálního prostředí projektu se obvykle nepoužívají správci balíčků (jako DNF). Místo toho jsou závislosti instalovány správci balíčků daného programovacího jazyka.
Ty jsou obvykle platformě nezávislé a používají vlastní repozitáře. Tyto repozitáře pak obsahují jen knihovny pro daný programovací jazyk. Opět může být těchto repozitářů více a je na vývojářích, jak si vše nastaví
V našem případě budeme vždy instalovat jen do adresářů virtuálního prostředí a do systému jako takového zasahovat nebudeme.
Python Package Index (PyPI)
Zbytek textu se bude zabývat Pythoními nástroji. Podobné nástroje jsou ale dostupné i pro další jazyky: věříme, že ukázky nad Pythonem jsou dostačující pro demonstraci principů v praxi.
Python má repozitář zvaný Python Package Index (PyPI), kde kdokoliv může zveřejnit své Pythonovské knihovny či programy.
Repozitář je možné používat buď skrz webový prohlížeč, nebo přes klienta na
příkazové řádce, který se jmenuje pip
.
pip
se chová podobně jako DNF.
Můžete s ním instalovat, upgradovat nebo odinstalovat Pythoní moduly.
Obvyklý průběh práce
Zatímco konkrétní nástroje se liší podle programovacího jazyka, základní kroky pro vývoj projektu uvnitř virtuálního projektu jsou v podstatě vždy stejné.
- Vývojář naklonuje projekt (třeba z Gitového repozitáře).
- Virtuální prostředí (sandbox) je inicializováno. To obvykle znamená vytvoření nového adresáře s čistým prostředím pro daný jazyk.
- Virtuální prostředí musí být aktivováno. Obvykle virtuální prostředí
musí změnit
$PATH
(nebo nějaký podobný ekvivalent, který je používán pro hledání knihoven a modulů), takže vývojář musísource
(nebo.
) nějaký aktivační skript, aby byly změněny příslušné proměnné. - Potom může vývojář nainstalovat závislosti. Obvykle jsou uloženy v souboru, který lze přímo předat správci balíčků (daného programovacího jazyka).
- Teprve teď může vývojář začít na projektu pracovat. Projekt je kompletně izolován, odstraněním adresáře s virtuálním prostředím odstraníme i veškeré stopy po instalovaných balíčcích.
Každodenní práce pak zahrnuje jen krok č. 3 (nějaký způsob aktivace) a krok 5 (vlastní vývoj).
Poznamenejme, že aktivace virtuálního prostředí obvykle odstraní přístup ke globálně (systémově) nainstalovaným knihovnám. Uvnitř virtuálního prostředí vývojář začíná s čistým štítem: jen s holým překladačem. To je ale velmi rozumné rozhodnutí: zajistí, že libovolná systémová instalace neovlivní nastavení projektu.
Jinými slovy to zlepší opakovatelnost (reproducibility) celého nastavení. Také to znamená, že vývojář musí uvést každou závislost do konfiguračního souboru. I pokud je to závislost, která je obvykle vždy nainstalovaná.
Virtuální prostředí pro Python (také známé jako virtualenv
nebo venv
)
Abychom si mohli bezpečně vyzkoušet instalaci balíčků Pythonu, nastavíme nejprve virtuální prostředí pro náš projekt. Python má naštěstí vestavěnou podporu pro vytvoření virtuálního prostředí.
Ukážeme si to na následujícím příkladu:
#!/usr/bin/env python3
import argparse
import shutil
import sys
import fs
class FsCatException(Exception):
pass
def fs_cat(filesystem, filename, target):
try:
with fs.open_fs(filesystem) as my_fs:
try:
with my_fs.open(filename, 'rb') as my_file:
shutil.copyfileobj(my_file, target)
except fs.errors.FileExpected as e:
raise FsCatException(f"{filename} on {filesystem} is not a regular file") from e
except fs.errors.ResourceNotFound as e:
raise FsCatException(f"{filename} does not exist on {filesystem}") from e
except Exception as e:
if isinstance(e, FsCatException):
raise e
raise FsCatException(f"unable to read {filesystem}, perhaps misspelled path or protocol ({e})?") from e
def main():
args = argparse.ArgumentParser(description='Filesystem cat')
args.add_argument(
'filesystem',
nargs=1,
metavar='FILESYSTEM',
help='Filesystem specification, e.g. tar://path/to/file.tar'
)
args.add_argument(
'filename',
nargs=1,
metavar='FILENAME',
help='File path on FILESYSTEM, e.g. /README.md'
)
config = args.parse_args()
try:
fs_cat(config.filesystem[0], config.filename[0], sys.stdout.buffer)
except FsCatException as e:
print(f"Fatal: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Uložte tento kód do souboru fscat.py
a nastavte mu spustitelný bit.
Všimněte si, že fs.open_fs
dokáže otevírat různé souborové systémy a
přistupovat k souborům v nich, jako byste použili vestavěný pythonovský
open
.
V našem programu zadáme cestu k souborovému systému a souboru (z tohoto
filesystému), který má být vypsán na obrazovku (odtud název fscat
, protože
simuluje cat
uvnitř jiného souborového systému).
Zkuste spustit program fscat.py
.
Pokud nemáte v systému nainstalovaný balíček python3-fs
, mělo by dojít k
chybě ModuleNotFoundError: No module named 'fs'
. Je pravděpodobné, že
tento modul nainstalovaný nemáte.
Pokud jste již nainstalovali python3-fs
, odinstalujte jej a zkuste to
znovu (jen pro tuto ukázku). Ale dvakrát zkontrolujte, zda byste
neodstranili nějaký jiný program, který jej může vyžadovat.
Nyní bychom mohli nainstalovat python3-fs
pomocí DNF, ale již jsme
vysvětlili, proč to není dobrý nápad. Mohli bychom jej také nainstalovat
globálně pomocí pip
, ale ani to není nejlepší postup.
Místo toho pro něj vytvoříme nové virtuální prostředí.
python3 -m venv my-venv
Výše uvedený příkaz vytvoří nový adresář my-venv
, který obsahuje čistou
instalaci Pythonu. Neváhejte a prozkoumejte obsah tohoto adresáře.
Nyní je třeba toto prostředí aktivovat.
source my-venv/bin/activate
Váš prompt by se měl změnit: nyní začíná (my-venv)
.
Spuštění souboru fscat.py
přesto skončí s chybou ModuleNotFoundError
.
Nyní nainstalujeme závislost:
pip install fs
To bude chvíli trvat, protože Python bude stahovat také tranzitivní
závislosti této knihovny (a jejich závislosti atd.). Po dokončení instalace
spusťte znovu soubor fscat.py
.
Tentokrát by to mělo fungovat.
./fscat.py
Dobře, vypsalo to chybovou zprávu o požadovaných argumentech. Stáhněte si tento archív a spusťte skript následujícím způsobem:
./fscat.py tar://test.tar.gz testdir/test.txt
Měl by vypsat Test string
, protože je schopen pracovat i s tar archívy
jako se souborovými systémy a vypsat na nich soubory (ověřte, zda je soubor
skutečně k dispozici, buď pomocí atool
, MC, nebo přímo pomocí tar
).
Jakmile jsme s vývojem hotovi, můžeme prostředí deaktivovat voláním
deactivate
(tentokrát bez zadávání zdrojů).
Spuštění souboru fscat.py
mimo prostředí opět skončí chybou
ModuleNotFoundError
.
Instalace balíčků specifických pro Python pomocí pip
Jedno použití pip
u v praxi jsme si již ukázali, ale pip
toho umí mnohem
více. Pěkný přehled všech možností pip
u najdete v článku Using Python’s
pip to Manage Your Projects'
Dependencies.
Zde uvádíme stručný přehled nejdůležitějších pojmů a příkazů.
Ve výchozím nastavení pip install
prohledává registr balíčků
PyPI, aby nainstaloval balíček zadaný na příkazovém
řádku. Nebyli bychom daleko od pravdy, kdybychom řekli, že všechny balíčky
uvnitř tohoto registru jsou jen archivované adresáře, které obsahují
předepsaným způsobem uspořádaný zdrojový kód v jazyce Python.
Pokud chcete tento výchozí registr balíčků změnit, můžete použít argument
--index-url
.
V další části se naučíme, jak z adresáře s kódem vytvořit správný balíček
Pythonu. Za předpokladu, že jsme to již udělali, můžeme tento balíček
nainstalovat přímo (bez archivace/balení) příkazem pip install /path/to/python_package
.
Představte si například situaci, kdy máte zájem o open-source balíček třetí
strany. Tento balíček je k dispozici ve vzdáleném repozitáři git (typicky
na GitHubu nebo GitLabu), ale NENÍ zabalen a zveřejněn v PyPI. Můžete
jednoduše klonovat repozitář a spustit pip install .
. Díky pip VCS
Support se však můžete
fázi klonování vyhnout a balíček nainstalovat přímo pomocí:
pip install git+https://git.example.com/MyProject
Chcete-li aktualizovat konkrétní balíček, spusťte příkaz pip install --upgrade [packages]
.
Nakonec pro odstranění balíčku spustíte pip uninstall [packages]
.
Verzování závislostí
Možná jste už slyšeli o sémantickém verzování. Python používá víceméně kompatibilní verzování, které je popsáno v PEP 440 – Version Identification and Dependency Specification.
Při instalaci závislostí z registru balíčků můžete tuto verzi zadat.
pkgname # latest version
pkgname == 4.2 # specific version
pkgname >= 4.2 # minimal version
pkgname ~= 4.2 # equivalent to >= 4.2, == 4.*
Pravdou je, že specifikátor verze se skládá z řady klauzulí o verzi oddělených čárkami. Proto můžete zadat:
pkgname >= 1.0, != 1.3.4.*, < 2.0
Někdy je užitečné uložit seznam všech aktuálně nainstalovaných balíčků (včetně tranzitivních závislostí). Například jste si nedávno všimli nové chyby ve svém projektu a rádi byste si uchovali přesné verze aktuálně nainstalovaných závislostí, aby váš spolupracovník mohl chybu reprodukovat.
Za tímto účelem je možné použít pip freeze
a vytvořit seznam, který
nastaví konkrétní verze a zajistí tak stejné prostředí pro každého vývojáře.
Doporučujeme je uložit do souboru requirements.txt
.
# Generating requirements file
pip freeze > requirements.txt
# Installing package from it
pip install -r requirements.txt
Balení Pythoních projektů
Řekněme, že jste přišli na super skvělý algoritmus a chcete obohatit svět tím, že se o něj podělíte. Oficiální dokumentace Pythonu nabízí návod krok za krokem, jak toho dosáhnout.
Struktura adresáře balíčků Pythonu
Úplně prvním krokem, než jej můžete publikovat, je jeho transformace do
správného balíčku Pythonu. Musíme vytvořit soubory pyproject.toml
a
setup.cfg
. Tyto soubory obsahují informace o projektu, seznam závislostí a
také informace pro instalaci projektu.
V repozitáři fscat najdete balíček Pythonu se stejnou funkčností, jakou má náš předchozí skript fscat.py
.
Prosím, podívejte se na adresářovou strukturu a obsah souboru setup.cfg. Nápověda.
Zkuste si tento balíček (s pomocí podpory VCS) následujícím příkazem nainstalovat:
pip install git+http://gitlab.mff.cuni.cz/teaching/nswi177/2025/common/fscat.git
Možná jste si všimli, že soubor setup.cfg
obsahuje sekci
[options.entry_points]
. Tato sekce určuje, jaké jsou skripty váš projekt
aktuálně obsahuje. Všimněte si, že po spuštění výše uvedeného příkazu
můžete spustit příkaz fscat
přímo. Pip pro vás vytvořil obalový skript a
přidal jej do $PATH
virtuálního prostředí.
fscat tar://tests/test.tar.gz testdir/test.txt
Nyní balíček odinstalujte pomocí:
pip uninstall matfyz-nswi177-fscat
Naklonujte repozitář do místního počítače a vstupte do jeho adresáře. Nyní spusťte:
pip install -e .
pip install -e
vytvoří editovatelnou instalaci pro snadné
ladění. Namísto kopírování vašeho kódu do virtuálního prostředí nainstaluje
pouze věc podobnou symlinku (ve skutečnosti soubor fscat.egg-link
, který
má podobný účinek na mechanismus Pythonu pro vyhledávání modulů) odkazující
na adresář s vašimi zdrojovými soubory.
Sestavení Pythoního balíčku
Publikování Pythoního balíčku
Pokud si myslíte, že by balíček mohl být užitečný i pro další lidi, můžete jej zveřejnit v Python Package Index. To se obvykle provádí pomocí nástroje twine. Přesný postup je popsán v části Uploading the distribution archives.
Nástroje vyšší úrovně
Na pip
a virtualenv
můžeme pohlížet jako na nízkoúrovňové nástroje.
Existují však i nástroje, které oba tyto nástroje kombinují a přinášejí
větší komfort při správě balíčků. V jazyce Python existují přinejmenším dvě
oblíbené možnosti, a to Poetry a
Pipenv.
Interně tyto nástroje používají pip
a venv
, takže stále můžete mít
nezávislé pracovní prostory a také možnost nainstalovat konkrétní balíček z
Python Package Index (PyPI).
Úplné představení těchto nástrojů je mimo rámec tohoto kurzu. Obecně se řídí stejnými principy, ale přidávají některé funkce navíc, které je hezké mít. Ve stručnosti lze uvést tyto hlavní rozdíly:
- Mohou zmrazit určité verze závislostí, takže se projekt sestaví na všech
počítačích stejně (pomocí souboru
poetry.lock
). - Balíčky lze odstranit spolu s jejich závislostmi.
- Inicializace nového projektu je jednodušší.
Ostatní jazyky
Ostatní jazyky mají vlastní nástroje s podobnými funkcemi:
Úlohy k ověření vašich znalostí
Očekáváme, že následující úlohy vyřešíte ještě před příchodem na cvičení, takže se budeme moci o vašich řešeních na cvičení pobavit.
Učební výstupy a kontrola po cvičení
Tato část podává zhuštěný souhrn základních konceptů a dovedností, které byste měli umět vysvětlit a/nebo použít po každém cvičení. Také obsahují absolutní minimum, které je potřebné pro pochopení navazujících cvičení (a dalších předmětů).
Znalosti konceptů
Znalost konceptů znamená, že rozumíte významu a kontextu daného tématu a jste schopni témata zasadit do většího rámce. Takže, jste schopni …
-
vysvětlit rozdíl mezi (normálním) přesměrování SSH portu a tzv. reverzním přesměrováním
-
vysvětlit, co jsou požadavky (závislosti na knihovnách)
-
vysvětlit základy sémantického verzování
-
vysvětlit, jaké jsou výhody a nevýhody instalace závislostí pro celý systém vs jejich instalaci ve virtuálním prostředí
-
poskytnout vysokoúrovňový přehled virtuálních prostředí
-
vysvětlit výhod a nevýhody uvedení tranzitivních závislostí ve srovnání s uvedením jen přímých
-
vysvětlit výhody a nevýhody uvedení přesné verze nebo jen minimální
Praktické dovednosti
Praktické dovednosti se obvykle týkají použití daných programů pro vyřešení různých úloh. Takže, dokážete …
-
použít přesměrování portu SSH pro přístup ke službě dostupné na zařízení loopback
-
použít reverzní předávání portů SSH pro připojení k počítači za NAT
-
vytvořit nové virtuální prostředí (pro Python) pomocí
python3 -m venv
-
aktivovat a deaktivovat virtuální prostředí
-
nainstalovat závislosti projektu do virtuálního prostředí pomocí
pip
-
vyvíjet program uvnitř virtuálního prostředí (s projektem používajícím
setup.cfg
apyproject.toml
) -
nainstalovat projekt, který používá
setup.cfg
-
volitelné: připravit projekt Pythonu pro instalaci