Repository: spotify/luigi

Issue:
PR on postgres.py broke backwards compat
reported by @duckontheweb on #2615 
```python
class MyQueryTask(luigi.contrib.postgres.PostgresQuery):
    host = ':'.join([db_host, db_port])
    database = db_name
    user = db_user
    password = db_password
```
This now raises an exception like the following:
```
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/luigi/worker.py", line 401, in check_complete
    is_complete = task.complete()
  File "/usr/local/lib/python3.6/site-packages/luigi/task.py", line 827, in complete
    return all(r.complete() for r in flatten(self.requires()))
  File "/usr/local/lib/python3.6/site-packages/luigi/task.py", line 827, in <genexpr>
    return all(r.complete() for r in flatten(self.requires()))
  File "/usr/local/lib/python3.6/site-packages/luigi/task.py", line 565, in complete
    outputs = flatten(self.output())
  File "/usr/local/lib/python3.6/site-packages/luigi/contrib/postgres.py", line 396, in output
    port=self.port
AttributeError: 'MyQueryTask' object has no attribute 'port'
```


Discussion:

Resolving patch:
diff --git a/luigi/contrib/postgres.py b/luigi/contrib/postgres.py
--- a/luigi/contrib/postgres.py
+++ b/luigi/contrib/postgres.py
@@ -363,7 +363,6 @@ class PostgresQuery(rdbms.Query):
 
     To customize the query signature as recorded in the database marker table, override the `update_id` property.
     """
-
     def run(self):
         connection = self.output().connect()
         connection.autocommit = self.autocommit
diff --git a/luigi/contrib/rdbms.py b/luigi/contrib/rdbms.py
--- a/luigi/contrib/rdbms.py
+++ b/luigi/contrib/rdbms.py
@@ -277,15 +277,29 @@ class Query(luigi.task.MixinNaiveBulkComplete, luigi.Task):
 
         Optionally override:
 
+        * `port`,
         * `autocommit`
+        * `update_id`
 
         Subclass and override the following methods:
 
+        * `run`
         * `output`
     """
 
     @abc.abstractproperty
     def host(self):
+        """
+        Host of the RDBMS. Implementation should support `hostname:port`
+        to encode port.
+        """
+        return None
+
+    @property
+    def port(self):
+        """
+        Override to specify port separately from host.
+        """
         return None
 
     @abc.abstractproperty
@@ -312,6 +326,13 @@ def query(self):
     def autocommit(self):
         return False
 
+    @property
+    def update_id(self):
+        """
+        Override to create a custom marker table 'update_id' signature for Query subclass task instances
+        """
+        return self.task_id
+
     @abc.abstractmethod
     def run(self):
         raise NotImplementedError("This method must be overridden")
@@ -322,10 +343,3 @@ def output(self):
         Override with an RDBMS Target (e.g. PostgresTarget or RedshiftTarget) to record execution in a marker table
         """
         raise NotImplementedError("This method must be overridden")
-
-    @property
-    def update_id(self):
-        """
-        Override to create a custom marker table 'update_id' signature for Query subclass task instances
-        """
-        return self.task_id


Test patch:
diff --git a/test/contrib/postgres_test.py b/test/contrib/postgres_test.py
--- a/test/contrib/postgres_test.py
+++ b/test/contrib/postgres_test.py
@@ -48,7 +48,6 @@ class DummyPostgresImporter(luigi.contrib.postgres.CopyToTable):
     date = luigi.DateParameter()
 
     host = 'dummy_host'
-    port = 1234
     database = 'dummy_database'
     user = 'dummy_user'
     password = 'dummy_password'
@@ -88,7 +87,6 @@ class DummyPostgresQuery(luigi.contrib.postgres.PostgresQuery):
     date = luigi.DateParameter()
 
     host = 'dummy_host'
-    port = 1234
     database = 'dummy_database'
     user = 'dummy_user'
     password = 'dummy_password'
@@ -100,6 +98,14 @@ class DummyPostgresQuery(luigi.contrib.postgres.PostgresQuery):
     query = 'SELECT * FROM foo'
 
 
+class DummyPostgresQueryWithPort(DummyPostgresQuery):
+    port = 1234
+
+
+class DummyPostgresQueryWithPortEncodedInHost(DummyPostgresQuery):
+    host = 'dummy_host:1234'
+
+
 @attr('postgres')
 class PostgresQueryTest(unittest.TestCase):
     maxDiff = None
@@ -124,6 +130,14 @@ def test_bulk_complete(self, mock_connect):
         ])
         self.assertFalse(task.complete())
 
+    def test_override_port(self):
+        output = DummyPostgresQueryWithPort(date=datetime.datetime(1991, 3, 24)).output()
+        self.assertEquals(output.port, 1234)
+
+    def test_port_encoded_in_host(self):
+        output = DummyPostgresQueryWithPortEncodedInHost(date=datetime.datetime(1991, 3, 24)).output()
+        self.assertEquals(output.port, '1234')
+
 
 @attr('postgres')
 class TestCopyToTableWithMetaColumns(unittest.TestCase):