Repository: PrefectHQ/prefect

Issue:
Warn users using `Docker` storage if their Python versions mismatch
Presumably this is easiest to do in the healthcheck, as users could be providing custom images with custom names.


Discussion:

Resolving patch:
diff --git a/src/prefect/core/flow.py b/src/prefect/core/flow.py
--- a/src/prefect/core/flow.py
+++ b/src/prefect/core/flow.py
@@ -1133,6 +1133,11 @@ def serialize(self, build: bool = False) -> dict:
                 raise ValueError("This flow has no storage to build")
             if self.name not in self.storage:
                 self.storage.add_flow(self)
+            else:
+                warnings.warn(
+                    "A flow with the same name is already contained in storage; if you changed your Flow since"
+                    " the last build, you might experience unexpected issues and should re-create your storage object."
+                )
             storage = self.storage.build()  # type: Optional[Storage]
         else:
             storage = self.storage
diff --git a/src/prefect/environments/storage/docker.py b/src/prefect/environments/storage/docker.py
--- a/src/prefect/environments/storage/docker.py
+++ b/src/prefect/environments/storage/docker.py
@@ -4,6 +4,7 @@
 import logging
 import os
 import shutil
+import sys
 import tempfile
 import textwrap
 import uuid
@@ -53,7 +54,15 @@ def __init__(
         prefect_version: str = None,
     ) -> None:
         self.registry_url = registry_url
-        self.base_image = base_image
+
+        if base_image is None:
+            python_version = "{}.{}".format(
+                sys.version_info.major, sys.version_info.minor
+            )
+            self.base_image = "python:{}".format(python_version)
+        else:
+            self.base_image = base_image
+
         self.image_name = image_name
         self.image_tag = image_tag
         self.python_dependencies = python_dependencies or []
@@ -169,9 +178,6 @@ def build(self, push: bool = True) -> "Storage":
                 where the flow is stored. Image name and tag are generated during the
                 build process.
         """
-        if not self.base_image:
-            self.base_image = "python:3.6"
-
         image_name, image_tag = self.build_image(push=push)
         self.image_name = image_name
         self.image_tag = image_tag
@@ -307,15 +313,23 @@ def create_dockerfile_object(self, directory: str = None) -> None:
                 """\
             print('Beginning health check...')
             import cloudpickle
+            import sys
+            import warnings
 
             for flow_file in [{flow_file_paths}]:
                 with open(flow_file, 'rb') as f:
                     flow = cloudpickle.load(f)
+
+            if sys.version_info.minor < {python_version}[1] or sys.version_info.minor > {python_version}[1]:
+                msg = "Your Docker container is using python version {{sys_ver}}, but your Flow was serialized using {{user_ver}}; this could lead to unexpected errors in deployment.".format(sys_ver=(sys.version_info.major, sys.version_info.minor), user_ver={python_version})
+                warnings.warn(msg)
+
             print('Healthcheck: OK')
             """.format(
                     flow_file_paths=", ".join(
                         ["'{}'".format(k) for k in self.flows.values()]
-                    )
+                    ),
+                    python_version=(sys.version_info.major, sys.version_info.minor),
                 )
             )
 


Test patch:
diff --git a/tests/core/test_flow.py b/tests/core/test_flow.py
--- a/tests/core/test_flow.py
+++ b/tests/core/test_flow.py
@@ -1407,7 +1407,11 @@ def test_serialize_can_be_called_twice(self):
         assert f.name not in f.storage
 
         f.serialize(build=True)
-        f.serialize(build=True)
+        with pytest.warns(UserWarning) as warning:
+            f.serialize(build=True)
+
+        w = warning.pop()
+        assert "already contained in storage" in str(w.message)
 
     def test_serialize_fails_with_no_storage(self):
         f = Flow(name="test")
diff --git a/tests/environments/storage/test_docker_storage.py b/tests/environments/storage/test_docker_storage.py
--- a/tests/environments/storage/test_docker_storage.py
+++ b/tests/environments/storage/test_docker_storage.py
@@ -1,4 +1,5 @@
 import os
+import sys
 import tempfile
 from unittest.mock import MagicMock
 
@@ -35,7 +36,7 @@ def test_empty_docker_storage():
     storage = Docker()
 
     assert not storage.registry_url
-    assert not storage.base_image
+    assert storage.base_image.startswith("python:")
     assert not storage.image_name
     assert not storage.image_tag
     assert not storage.python_dependencies
@@ -45,7 +46,15 @@ def test_empty_docker_storage():
     assert storage.base_url == "unix://var/run/docker.sock"
 
 
-def test_empty_docker_storage():
+@pytest.mark.parametrize("version_info", [(3, 5), (3, 6), (3, 7)])
+def test_docker_init_responds_to_python_version(monkeypatch, version_info):
+    version_mock = MagicMock(major=version_info[0], minor=version_info[1])
+    monkeypatch.setattr(sys, "version_info", version_mock)
+    storage = Docker()
+    assert storage.base_image == "python:{}.{}".format(*version_info)
+
+
+def test_initialized_docker_storage():
     storage = Docker(
         registry_url="test1",
         base_image="test3",