Airflow xcom pull retorna apenas string
Eu tenho um pipeline de fluxo de ar onde preciso obter um nome de arquivo de uma assinatura pubsub e, em seguida, importar esse arquivo para uma instância de sql em nuvem. Eu uso o CloudSqlInstanceImportOperator para importar o arquivo CSV. Este operador precisa de um corpo, que contém o nome do arquivo e outros parâmetros. Desde que li esse nome de arquivo durante a execução, também tenho que definir o corpo durante a execução. Tudo isso funciona. Mas quando puxo o corpo de xcom, ele retorna uma string em vez de um dicionário python. Portanto, o CloudSqlInstanceImportOperator me dá o seguinte erro (meu palpite é, porque o corpo é uma string e não um dicionário):
Traceback (most recent call last)
File "/usr/local/lib/airflow/airflow/models/taskinstance.py", line 984, in _run_raw_tas
result = task_copy.execute(context=context
File "/usr/local/lib/airflow/airflow/contrib/operators/gcp_sql_operator.py", line 715, in execut
self._validate_body_fields(
File "/usr/local/lib/airflow/airflow/contrib/operators/gcp_sql_operator.py", line 712, in _validate_body_field
api_version=self.api_version).validate(self.body
File "/usr/local/lib/airflow/airflow/contrib/utils/gcp_field_validator.py", line 420, in validat
dictionary_to_validate=body_to_validate
File "/usr/local/lib/airflow/airflow/contrib/utils/gcp_field_validator.py", line 341, in _validate_fiel
value = dictionary_to_validate.get(field_name
AttributeError: 'str' object has no attribute 'get
Este é o código que uso:
import json
import os
from datetime import datetime, timedelta
import ast
from airflow import DAG
from airflow.contrib.operators.gcs_to_gcs import GoogleCloudStorageToGoogleCloudStorageOperator
from airflow.operators.python_operator import PythonOperator
from airflow.operators.bash_operator import BashOperator
from airflow.contrib.sensors.pubsub_sensor import PubSubPullSensor
from airflow.contrib.sensors.gcs_sensor import GoogleCloudStoragePrefixSensor
from airflow.operators.dagrun_operator import TriggerDagRunOperator
from airflow.contrib.operators.gcp_sql_operator import CloudSqlInstanceImportOperator
def create_dag(dag_id,default_args):
BUCKET = "{{ var.value.gp2pg_bucket }}"
GCP_PROJECT_ID = "{{ var.value.gp2pg_project_id }}"
INSTANCE_NAME = "{{ var.value.gp2pg_instance_name }}"
def define_import_body(file,**kwargs):
import_body = {
"importContext": {
"importUser": "databasename",
"database": "databaseuser",
"fileType": "csv",
"uri": "bucketname" + file,
"csvImportOptions": {
"table": "schema.tablename",
"columns": ["columns1",
"column2"]}
}
}
task_instance = kwargs['task_instance']
task_instance.xcom_push(key='import_body', value=import_body)
print(import_body)
def get_filename(var,**kwargs):
message = ast.literal_eval(var)
file = message[0].get('message').get('attributes').get('objectId')
task_instance = kwargs['task_instance']
task_instance.xcom_push(key='filename', value=file)
print(file)
dag = DAG(dag_id=dag_id, schedule_interval=None, default_args=default_args)
with dag:
t1 = PubSubPullSensor(task_id='pull-messages',
project="projectname",
ack_messages=True,
max_messages=1,
subscription="subscribtionname")
message = "{{ task_instance.xcom_pull() }}"
t2 = PythonOperator(
task_id='get_filename',
python_callable=get_filename,
op_kwargs={'var': message},
provide_context=True,
)
file = "{{ task_instance.xcom_pull(task_ids='get_filename', key='filename') }}"
t3 = PythonOperator(
task_id='define_import_body',
python_callable=define_import_body,
op_kwargs={'file': file},
provide_context=True,
)
import_body = "{{ task_instance.xcom_pull(task_ids='define_import_body', key='import_body') }}"
t4 = CloudSqlInstanceImportOperator(
project_id=GCP_PROJECT_ID,
body= import_body,
instance=INSTANCE_NAME,
gcp_conn_id='postgres_default',
task_id='sql_import_task',
validate_body=True,
)
t5 = GoogleCloudStorageToGoogleCloudStorageOperator(
task_id='copy_files',
source_bucket=BUCKET,
source_object=file,
destination_bucket=BUCKET,
destination_object='processed/import/'+file, )
t1 >> t2 >> t3 >> t4 >> t5
return dag
dags_folder = os.getenv('DAGS_FOLDER', "./dags")
flow_config = open(f'{dags_folder}/gp2pg/flow_config.json', 'r').read()
for key, values in json.loads(flow_config).items():
default_args = {
"owner": "owner",
"start_date": datetime(2020, 1, 1),
"email": [],
"email_on_failure": False,
"email_on_retry": False,
"retries": 0,
"retry_delay": timedelta(minutes=5),
}
dag_id = f"gp2pg_{key}_data_to_pg"
globals()[dag_id] = create_dag(dag_id, default_args)
Alguma ideia de como eu poderia resolver esse problema?
Respostas
O primeiro CloudSqlInstanceImportOperatorestá obsoleto . Você deve usar CloudSQLImportInstanceOperator de provedores
O bodyparâmetro precisa ser definido conforme explicado nos documentos .
XCOM é uma tabela no banco de dados. Os dados são salvos como strings. Você não pode armazenar dict no banco de dados, pois dict é um objeto Python na memória. Você provavelmente tem um Json (string). Tente convertê-lo para dict:
body= json.loads(import_body)
EDITAR: (após discussão nos comentários)
Você precisará envolver seu operador com PythonOperator para que possa converter o xcompara dict e usá-lo.
def my_func(ds, **kwargs):
ti = kwargs['ti']
body = ti.xcom_pull(task_ids='privious_task_id')
import_body= json.loads(body)
op = CloudSqlInstanceImportOperator(
project_id=GCP_PROJECT_ID,
body= import_body,
instance=INSTANCE_NAME,
gcp_conn_id='postgres_default',
task_id='sql_import_task',
validate_body=True,
)
op.execute()
p = PythonOperator(task_id='python_task', python_callable=my_func)