Repository: PrefectHQ/prefect

Issue:
Same flow generates different hashes on repeated runs
## Description
`flow.serialized_hash()` generates different values on different script runs. Based on the original feature description this isn't what I expected.


## Expected Behavior
The exact same flow will always generate the same hash.




## Reproduction
<!-- A minimal example that exhibits the behavior. -->

Run this **twice** as a module executing as a script (`python -m`) and observe different hashes in between runs:

```python
from prefect import task, Flow


@task
def dummy_task():
    return "nothing interesting"


with Flow("example-flow") as flow:
    dummy_task()


def main():
    print(flow.serialized_hash())
    print(flow.serialized_hash())


if __name__ == "__main__":
    main()
```


## Environment
```
{
  "config_overrides": {
    "backend": true,
    "server": {
      "host": true,
      "telemetry": {
        "enabled": true
      },
      "ui": {
        "apollo_url": true,
        "host": true
      }
    }
  },
  "env_vars": [],
  "system_information": {
    "platform": "Windows-10-10.0.19041-SP0",
    "prefect_backend": "server",
    "prefect_version": "0.13.14",
    "python_version": "3.8.5"
  }
}
```

and also with

```
{
  "config_overrides": {
    "server": {
      "host": true,
      "telemetry": {
        "enabled": true
      },
      "ui": {
        "apollo_url": true
      }
    }
  },
  "env_vars": [],
  "system_information": {
    "platform": "Linux-4.15.0-118-generic-x86_64-with-debian-buster-sid",
    "prefect_backend": "cloud",
    "prefect_version": "0.13.15",
    "python_version": "3.7.7"
  }
}
```



Discussion:
@madkinsz 

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
@@ -1478,7 +1478,9 @@ def serialized_hash(self, build: bool = False) -> str:
         Returns:
             - str: the hash of the serialized flow
         """
-        return hashlib.sha256(json.dumps(self.serialize(build)).encode()).hexdigest()
+        return hashlib.sha256(
+            json.dumps(self.serialize(build), sort_keys=True).encode()
+        ).hexdigest()
 
     # Diagnostics  ----------------------------------------------------------------
 


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
@@ -7,6 +7,8 @@
 import sys
 import tempfile
 import time
+import subprocess
+import textwrap
 from unittest.mock import MagicMock, patch
 
 import cloudpickle
@@ -1859,6 +1861,35 @@ def test_is_different_before_and_after_storage_build(self):
     def test_is_different_with_different_flow_name(self):
         assert Flow("foo").serialized_hash() != Flow("bar").serialized_hash()
 
+    def test_is_same_in_new_python_instance(self, tmpdir):
+        contents = textwrap.dedent(
+            """
+        from prefect import task, Flow
+
+        @task
+        def dummy_task():
+            return "nothing interesting"
+
+        with Flow("example-flow") as flow:
+            dummy_task()
+
+        if __name__ == "__main__":
+            print(flow.serialized_hash())
+        """
+        )
+        script = tmpdir.join("flow.py")
+        script.write_text(contents, encoding="utf-8")
+
+        hashes = []
+        for _ in range(2):
+            result = subprocess.run(
+                [sys.executable, script], stdout=subprocess.PIPE, check=True
+            )
+            hashes.append(result.stdout)
+
+        assert hashes[0]  # Ensure we don't have an empty string or None
+        assert len(set(hashes)) == 1
+
     def test_is_different_with_modified_flow_name(self):
         f1 = Flow("foo")
         f2 = f1.copy()