J'ai plusieurs fichiers python que j'exécute actuellement avec BashOperator. Cela me permet de choisir facilement l’environnement virtuel python.
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
default_args = {
'owner': 'airflow',
'depends_on_past': False,
...}
dag = DAG('python_tasks', default_args=default_args, schedule_interval="23 4 * * *")
t1 = BashOperator(
task_id='task1',
bash_command='~/anaconda3/envs/myenv/bin/python
/python_files/python_task1.py',
dag=dag)
Comment puis-je obtenir le même résultat avec PythonOperator?
from airflow.operators.bash_operator import PythonOperator
import python_files.python_task1
python_task = PythonOperator(
task_id='python_task',
python_callable=python_task1.main,
dag=dag)
Je suppose que PythonOperator utilisera l'environnement python du système. J'ai constaté qu'Airflow dispose de PythonVirtualenvOperator, mais cela semble fonctionner en créant un nouvel env virtuel à la volée en utilisant les exigences spécifiées. Je préférerais en utiliser un qui est déjà configuré correctement. Comment puis-je exécuter PythonOperator avec un chemin python spécifié?
Tout d'abord: vous ne devez pas (en général) compter sur des ressources préexistantes pour vos opérateurs. Vos opérateurs doivent être portables, l’utilisation de virtualenvs de longue date va quelque peu à l’encontre de ce principe. Cela étant dit, ce n'est pas si grave, tout comme vous devez préinstaller des paquets dans un environnement global, vous pouvez préchauffer quelques environnements. Vous pouvez également laisser l’opérateur créer l’environnement et les opérateurs ultérieurs peuvent le réutiliser, ce qui, à mon avis, est l’approche la plus facile et la plus dangereuse.
Implémenter un "cache virtualenv" ne devrait pas être difficile. Lecture de l'implémentation de la méthode d'exécution de PythonVirtualenvOperator
:
def execute_callable(self):
with TemporaryDirectory(prefix='venv') as tmp_dir:
...
self._execute_in_subprocess(
self._generate_python_cmd(tmp_dir,
script_filename,
input_filename,
output_filename,
string_args_filename))
return self._read_result(output_filename)
Donc, il semble que cela ne supprime pas explicitement virtualenv (il repose sur TemporaryDirectory
pour le faire). Vous pouvez sous-classer PythonVirtualenvOperator
et simplement utiliser votre propre gestionnaire de contexte qui réutilise les répertoires temporaires:
import glob
@contextmanager
def ReusableTemporaryDirectory(prefix):
try:
existing = glob.glob('/tmp/' + prefix + '*')
if len(existing):
name = existing[0]
else:
name = mkdtemp(prefix=prefix)
yield name
finally:
# simply don't delete the tmp dir
pass
def execute_callable(self):
with ReusableTemporaryDirectory(prefix='cached-venv') as tmp_dir:
...
Naturellement, vous pouvez supprimer le try-finally
dans ReusableTemporaryDirectory
et remettre les arguments habituels suffix
et dir
. J'ai apporté des modifications minimes pour faciliter la comparaison avec la classe TemporaryDirectory
d'origine.
Avec cela, votre virtualenv ne sera pas ignoré mais de nouvelles dépendances seront éventuellement installées par l'opérateur.