Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion luigi/contrib/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@

try:
import boto3

_boto3_enabled = True
except ImportError:
logger.warning("boto3 is not installed. BatchTasks require boto3")
_boto3_enabled = False


class BatchJobException(Exception):
Expand All @@ -88,6 +90,8 @@ def _random_id():

class BatchClient:
def __init__(self, poll_time=POLL_TIME):
if not _boto3_enabled:
raise ImportError("boto3 is required for Batch functionality. Install it with: pip install boto3")
self.poll_time = poll_time
self._client = boto3.client("batch")
self._log_client = boto3.client("logs")
Expand Down Expand Up @@ -193,6 +197,11 @@ class BatchTask(luigi.Task):
job_queue = luigi.OptionalParameter(default=None)
poll_time = luigi.IntParameter(default=POLL_TIME)

def __init__(self, *args, **kwargs):
if not _boto3_enabled:
raise ImportError("boto3 is required for Batch functionality. Install it with: pip install boto3")
super().__init__(*args, **kwargs)

def run(self):
bc = BatchClient(self.poll_time)
job_id = bc.submit_job(self.job_definition, self.parameters, job_name=self.job_name, queue=self.job_queue)
Expand Down
8 changes: 7 additions & 1 deletion luigi/contrib/bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
try:
import httplib2
from googleapiclient import discovery, errors, http

_bigquery_enabled = True
except ImportError:
logger.warning("BigQuery module imported, but google-api-python-client is not installed. Any BigQuery task will fail")
_bigquery_enabled = False
else:
RETRYABLE_ERRORS = (httplib2.HttpLib2Error, IOError, TimeoutError, BrokenPipeError)

Expand Down Expand Up @@ -142,6 +144,8 @@ class BigQueryClient:
"""

def __init__(self, oauth_credentials=None, descriptor="", http_=None):
if not _bigquery_enabled:
raise ImportError("google-api-python-client is required for BigQuery functionality. Install it with: pip install google-api-python-client")
# Save initialisation arguments in case we need to re-create client
# due to connection timeout
self.oauth_credentials = oauth_credentials
Expand Down Expand Up @@ -398,6 +402,8 @@ def copy(self, source_table, dest_table, create_disposition=CreateDisposition.CR

class BigQueryTarget(luigi.target.Target):
def __init__(self, project_id, dataset_id, table_id, client=None, location=None):
if not _bigquery_enabled:
raise ImportError("google-api-python-client is required for BigQuery functionality. Install it with: pip install google-api-python-client")
self.table = BQTable(project_id=project_id, dataset_id=dataset_id, table_id=table_id, location=location)
self.client = client or BigQueryClient()

Expand Down
9 changes: 8 additions & 1 deletion luigi/contrib/bigquery_avro.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
try:
import avro
import avro.datafile

_avro_enabled = True
except ImportError:
logger.warning("bigquery_avro module imported, but avro is not installed. Any BigQueryLoadAvro task will fail to propagate schema documentation")
_avro_enabled = False


class BigQueryLoadAvro(BigQueryLoadTask):
Expand All @@ -30,6 +32,11 @@ class BigQueryLoadAvro(BigQueryLoadTask):

source_format = SourceFormat.AVRO

def __init__(self, *args, **kwargs):
if not _avro_enabled:
raise ImportError("avro is required for BigQueryLoadAvro. Install it with: pip install avro-python3")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indirectly related to this PR avro-python3 is deprecated in favor of avro. Something worth addressing separately.

super().__init__(*args, **kwargs)

def _avro_uri(self, target):
path_or_uri = target.uri if hasattr(target, "uri") else target.path
return path_or_uri if path_or_uri.endswith(".avro") else path_or_uri.rstrip("/") + "/*.avro"
Expand Down
6 changes: 5 additions & 1 deletion luigi/contrib/datadog_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

try:
from datadog import api, initialize, statsd

_datadog_enabled = True
except ImportError:
logger.warning("Loading datadog module without datadog installed. Will crash at runtime if datadog functionality is used.")
_datadog_enabled = False


class datadog(Config):
Expand All @@ -24,6 +26,8 @@ class datadog(Config):

class DatadogMetricsCollector(MetricsCollector):
def __init__(self, *args, **kwargs):
if not _datadog_enabled:
raise ImportError("datadog is required for DatadogMetricsCollector. Install it with: pip install datadog")
self._config = datadog(**kwargs)

initialize(api_key=self._config.api_key, app_key=self._config.app_key, statsd_host=self._config.statsd_host, statsd_port=self._config.statsd_port)
Expand Down
11 changes: 7 additions & 4 deletions luigi/contrib/dataproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@
DEFAULT_CREDENTIALS, _ = google.auth.default()
authenticate_kwargs = gcp.get_authenticate_kwargs(DEFAULT_CREDENTIALS)
_dataproc_client = discovery.build("dataproc", "v1", cache_discovery=False, **authenticate_kwargs)
_dataproc_enabled = True
except ImportError:
logger.warning(
"Loading Dataproc module without the python packages googleapiclient & google-auth. \
This will crash at runtime if Dataproc functionality is used."
)
_dataproc_enabled = False


def get_dataproc_client():
Expand All @@ -42,6 +40,11 @@ class _DataprocBaseTask(luigi.Task):

dataproc_client = get_dataproc_client()

def __init__(self, *args, **kwargs):
if not _dataproc_enabled:
raise ImportError("google-api-python-client is required for Dataproc functionality. Install it with: pip install google-api-python-client")
Comment thread
dlstadther marked this conversation as resolved.
Outdated
super().__init__(*args, **kwargs)


class DataprocBaseTask(_DataprocBaseTask):
"""
Expand Down
6 changes: 4 additions & 2 deletions luigi/contrib/docker_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
import docker
from docker.errors import APIError, ContainerError, ImageNotFound

_docker_enabled = True
except ImportError:
logger.warning("docker is not installed. DockerTask requires docker.")
docker = None
_docker_enabled = False

# TODO: may need to implement this logic for remote hosts
# class dockerconfig(luigi.Config):
Expand Down Expand Up @@ -143,6 +143,8 @@ def __init__(self, *args, **kwargs):
- create a tmp dir
- add the temp dir to the volume binds specified in the task
"""
if not _docker_enabled:
raise ImportError("docker is required for DockerTask. Install it with: pip install docker")
super(DockerTask, self).__init__(*args, **kwargs)
self.__logger = logger

Expand Down
10 changes: 7 additions & 3 deletions luigi/contrib/dropbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
import dropbox.dropbox_client
import dropbox.exceptions
import dropbox.files

_dropbox_enabled = True
except ImportError:
logger.warning(
"Loading Dropbox module without the python package dropbox (https://pypi.org/project/dropbox/). Will crash at runtime if Dropbox functionality is used."
)
_dropbox_enabled = False


def accept_trailing_slash_in_existing_dirpaths(func):
Expand Down Expand Up @@ -74,6 +74,8 @@ def __init__(self, token, user_agent="Luigi", root_namespace_id=None):
:param str token: Dropbox Oauth2 Token. See :class:`DropboxTarget` for more information about generating a token
:param str root_namespace_id: Root namespace ID for interacting with Team Spaces
"""
if not _dropbox_enabled:
raise ImportError("dropbox is required for Dropbox functionality. Install it with: pip install dropbox")
if not token:
raise ValueError("The token parameter must contain a valid Dropbox Oauth2 Token")

Expand Down Expand Up @@ -292,6 +294,8 @@ def __init__(self, path, token, format=None, user_agent="Luigi", root_namespace_


"""
if not _dropbox_enabled:
raise ImportError("dropbox is required for Dropbox functionality. Install it with: pip install dropbox")
super(DropboxTarget, self).__init__(path)

if not token:
Expand Down
8 changes: 7 additions & 1 deletion luigi/contrib/ecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@
import boto3

client = boto3.client("ecs")
_boto3_enabled = True
except ImportError:
logger.warning("boto3 is not installed. ECSTasks require boto3")
_boto3_enabled = False

POLL_TIME = 2

Expand Down Expand Up @@ -138,6 +139,11 @@ class ECSTask(luigi.Task):
task_def = luigi.OptionalParameter(default=None)
cluster = luigi.Parameter(default="default")

def __init__(self, *args, **kwargs):
if not _boto3_enabled:
raise ImportError("boto3 is required for ECSTask. Install it with: pip install boto3")
super().__init__(*args, **kwargs)

@property
def ecs_task_ids(self):
"""Expose the ECS task ID"""
Expand Down
5 changes: 4 additions & 1 deletion luigi/contrib/esindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ def docs(self):
from elasticsearch.connection import Urllib3HttpConnection
from elasticsearch.helpers import bulk

_elasticsearch_enabled = True
except ImportError:
logger.warning("Loading esindex module without elasticsearch installed. Will crash at runtime if esindex functionality is used.")
_elasticsearch_enabled = False


class ElasticsearchTarget(luigi.Target):
Expand Down Expand Up @@ -129,6 +130,8 @@ def __init__(self, host, port, index, doc_type, update_id, marker_index_hist_siz
:param extra_elasticsearch_args: extra args for Elasticsearch
:type Extra: dict
"""
if not _elasticsearch_enabled:
raise ImportError("elasticsearch is required for ElasticsearchTarget. Install it with: pip install elasticsearch")
if extra_elasticsearch_args is None:
extra_elasticsearch_args = {}

Expand Down
2 changes: 1 addition & 1 deletion luigi/contrib/ftp.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _sftp_connect(self):
try:
import pysftp
except ImportError:
logger.warning("Please install pysftp to use SFTP.")
raise ImportError("pysftp is required for SFTP functionality. Install it with: pip install pysftp")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside of scope - pysftp hasn't been updated in a very long time. Should probably be replaced with paramiko.


self.conn = pysftp.Connection(self.host, username=self.username, password=self.password, port=self.port, **self.pysftp_conn_kwargs)

Expand Down
9 changes: 5 additions & 4 deletions luigi/contrib/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
try:
import google.auth
import httplib2

_gcp_enabled = True
except ImportError:
logger.warning(
"Loading GCP module without the python packages httplib2, google-auth. \
This *could* crash at runtime if no other credentials are provided."
)
_gcp_enabled = False


def get_authenticate_kwargs(oauth_credentials=None, http_=None):
Expand All @@ -25,6 +24,8 @@ def get_authenticate_kwargs(oauth_credentials=None, http_=None):

Used by `gcs.GCSClient` and `bigquery.BigQueryClient` to initiate the API Client
"""
if not _gcp_enabled:
raise ImportError("google-auth and httplib2 are required for GCP functionality. Install them with: pip install google-auth httplib2")
Comment thread
dlstadther marked this conversation as resolved.
Outdated
if oauth_credentials:
authenticate_kwargs = {"credentials": oauth_credentials}
elif http_:
Expand Down
13 changes: 9 additions & 4 deletions luigi/contrib/gcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@
try:
import httplib2
from googleapiclient import discovery, errors, http

_gcs_enabled = True
except ImportError:
logger.warning(
"Loading GCS module without the python packages googleapiclient & google-auth. \
This will crash at runtime if GCS functionality is used."
)
_gcs_enabled = False
else:
RETRYABLE_ERRORS = (httplib2.HttpLib2Error, IOError)

Expand Down Expand Up @@ -122,6 +121,8 @@ class GCSClient(luigi.target.FileSystem):
"""

def __init__(self, oauth_credentials=None, descriptor="", http_=None, chunksize=CHUNKSIZE, **discovery_build_kwargs):
if not _gcs_enabled:
raise ImportError("googleapiclient is required for GCS functionality. Install it with: pip install google-api-python-client")
self.chunksize = chunksize
authenticate_kwargs = gcp.get_authenticate_kwargs(oauth_credentials, http_)

Expand Down Expand Up @@ -435,6 +436,8 @@ class GCSTarget(luigi.target.FileSystemTarget):
fs = None

def __init__(self, path, format=None, client=None):
if not _gcs_enabled:
raise ImportError("googleapiclient is required for GCS functionality. Install it with: pip install google-api-python-client")
super(GCSTarget, self).__init__(path)
if format is None:
format = luigi.format.get_default_format()
Expand Down Expand Up @@ -484,6 +487,8 @@ def __init__(self, path, format=None, client=None, flag="_SUCCESS"):
:param flag:
:type flag: str
"""
if not _gcs_enabled:
raise ImportError("googleapiclient is required for GCS functionality. Install it with: pip install google-api-python-client")
if format is None:
format = luigi.format.get_default_format()

Expand Down
9 changes: 8 additions & 1 deletion luigi/contrib/kubernetes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
from pykube.config import KubeConfig
from pykube.http import HTTPClient
from pykube.objects import Job, Pod

_pykube_enabled = True
except ImportError:
logger.warning("pykube is not installed. KubernetesJobTask requires pykube.")
_pykube_enabled = False


class kubernetes(luigi.Config):
Expand All @@ -62,6 +64,11 @@ class KubernetesJobTask(luigi.Task):
__DEFAULT_POD_CREATION_INTERVAL = 5
_kubernetes_config = None # Needs to be loaded at runtime

def __init__(self, *args, **kwargs):
if not _pykube_enabled:
raise ImportError("pykube is required for KubernetesJobTask. Install it with: pip install pykube-ng")
super().__init__(*args, **kwargs)

def _init_kubernetes(self):
self.__logger = logger
self.__logger.debug("Kubernetes auth method: " + self.auth_method)
Expand Down
9 changes: 5 additions & 4 deletions luigi/contrib/mssqldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@

try:
from pymssql import _mssql

_pymssql_enabled = True
except ImportError:
logger.warning(
"Loading MSSQL module without the python package pymssql. \
This will crash at runtime if SQL Server functionality is used."
)
_pymssql_enabled = False


class MSSqlTarget(luigi.Target):
Expand Down Expand Up @@ -56,6 +55,8 @@ def __init__(self, host, database, user, password, table, update_id):
:param update_id: an identifier for this data set.
:type update_id: str
"""
if not _pymssql_enabled:
raise ImportError("pymssql is required for SQL Server functionality. Install it with: pip install pymssql")
if ":" in host:
self.host, self.port = host.split(":")
self.port = int(self.port)
Expand Down
9 changes: 5 additions & 4 deletions luigi/contrib/mysqldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@
try:
import mysql.connector
from mysql.connector import Error, errorcode

_mysql_enabled = True
except ImportError:
logger.warning(
"Loading MySQL module without the python package mysql-connector-python. \
This will crash at runtime if MySQL functionality is used."
)
_mysql_enabled = False


class MySqlTarget(luigi.Target):
Expand All @@ -56,6 +55,8 @@ def __init__(self, host, database, user, password, table, update_id, **cnx_kwarg
:param cnx_kwargs: optional params for mysql connector constructor.
See https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html.
"""
if not _mysql_enabled:
raise ImportError("mysql-connector-python is required for MySQL functionality. Install it with: pip install mysql-connector-python")
if ":" in host:
self.host, self.port = host.split(":")
self.port = int(self.port)
Expand Down
5 changes: 4 additions & 1 deletion luigi/contrib/pai.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@
import requests as rs
from requests.exceptions import HTTPError

_requests_enabled = True
except ImportError:
logger.warning("requests is not installed. PaiTask requires requests.")
_requests_enabled = False


def slot_to_dict(o):
Expand Down Expand Up @@ -240,6 +241,8 @@ def __init__(self, *args, **kwargs):
:param pai_url: The rest server url of PAI clusters, default is 'http://127.0.0.1:9186'.
:param token: The token used to auth the rest server of PAI.
"""
if not _requests_enabled:
raise ImportError("requests is required for PaiTask. Install it with: pip install requests")
super(PaiTask, self).__init__(*args, **kwargs)
self.__init_token()

Expand Down
Loading
Loading