Repository: PrefectHQ/prefect
Pull request: Add check for `prefect-client` version in prefect-client workflow
<!-- 
Thanks for opening a pull request to Prefect! 
If this is your first contribution, please make sure to review our contribution guidelines: https://docs.prefect.io/latest/contributing/overview/
-->

<!-- Include an overview of the proposed changes here -->
Fixes an issue where the `__version__` attribute is not populating correctly. The root cause is that the `setup.py` that the `prefect-client` package uses doe not include a `cmdclass`. That `cmdclass` generates the `_version.py` file that encodes the current version when we build the library. It was likely left out because we delete files in CI, which leads to `versioneer` adding a `.dirty` to the generated version.

After trying several other approaches, I changed the version style that `versioneer` uses for version generation to `pep440-pre`, which ignores dirty changes. This isn't ideal since we lose information with this style, but it does fix the issue. We should consider alternative ways of building `prefect-client` that don't require such changes.

I also added a check to ensure that `prefect-client` correctly matches the version of `prefect` from this point on.

Closes https://github.com/PrefectHQ/prefect/issues/15620


Problem statement:
prefect-client has unknown prefect.__version__
### Bug summary

Attempt to run deployed flow via prefect-client==3.0.5 produces this error:
`RuntimeError: Found incompatible versions: client: 0+unknown, server: 3.0.5. Major versions must match.`

```
python -m pip install prefect-client==3.0.5
python -c 'import prefect; print(prefect.__version__)'
```
outputs `0+unknown` which might be the problem.

### Version info (`prefect version` output)

```Text
There is no `prefect` cli in `prefect-client`
3.0.5 / 0+unknown
```


### Additional context

_No response_

Hints from issue discussion:
Thanks for the bug report, @ikseek! It looks like a version check was introduced in 3.0.1 via [#15252](https://github.com/PrefectHQ/prefect/pull/15252), which is where this error is coming from. The missing version on `prefect-client` looks like a long-standing issue that wasn't discovered until now. I will investigate and implement a fix!

Commit message: Fix markdown tests

Patch to review:
diff --git a/.github/workflows/markdown-tests.yaml b/.github/workflows/markdown-tests.yaml
index 7a75a126c2e6..495dfa0a1bbb 100644
--- a/.github/workflows/markdown-tests.yaml
+++ b/.github/workflows/markdown-tests.yaml
@@ -95,7 +95,7 @@ jobs:
 
       - name: Start server
         run: |
-          prefect server start&
+          PREFECT_HOME=$(pwd) prefect server start&
           PREFECT_API_URL="http://127.0.0.1:4200/api" ./scripts/wait-for-server.py
 
       - name: Run tests
diff --git a/.github/workflows/prefect-client.yaml b/.github/workflows/prefect-client.yaml
index fbeda01bd71f..2aaee333fe97 100644
--- a/.github/workflows/prefect-client.yaml
+++ b/.github/workflows/prefect-client.yaml
@@ -10,6 +10,7 @@ on:
       - requirements.txt
       - requirements-client.txt
       - setup.cfg
+      - .github/workflows/prefect-client.yaml
   push:
     branches:
       - main
@@ -19,6 +20,7 @@ on:
       - requirements.txt
       - requirements-client.txt
       - setup.cfg
+      - .github/workflows/prefect-client.yaml
   workflow_call:
     inputs:
       upload-artifacts:
@@ -56,6 +58,10 @@ jobs:
         env:
           TMPDIR: ${{ env.TMPDIR }}
 
+      - name: (DEBUG) Print versioneer output
+        run: python -c "import versioneer; print(versioneer.get_version())"
+        working-directory: ${{ env.TMPDIR }}
+
       - name: Build a binary wheel and a source tarball
         run: pip install wheel && python setup.py sdist bdist_wheel
         working-directory: ${{ env.TMPDIR }}
@@ -64,6 +70,13 @@ jobs:
         run: pip install dist/*.tar.gz
         working-directory: ${{ env.TMPDIR }}
 
+      - name: Get the version of built `prefect-client`
+        run: |
+          prefect_client_version=$(python -c "import prefect; print(prefect.__version__)")
+          echo "prefect_client_version=$prefect_client_version" >> $GITHUB_OUTPUT
+        working-directory: ${{ env.TMPDIR }}
+        id: prefect_client_version
+
       - name: Run the smoke test flow using the built client
         run: python client/client_flow.py
         working-directory: ${{ env.TMPDIR }}
@@ -74,8 +87,18 @@ jobs:
       - name: Install prefect from source
         run: pip install .
 
-      - name: (DEBUG) Check that prefect and prefect-client are installed
-        run: pip list | grep prefect
+      - name: Get the version of built `prefect`
+        run: |
+          prefect_version=$(prefect --version)
+          echo "prefect_version=$prefect_version" >> $GITHUB_OUTPUT
+        id: prefect_version
+
+      - name: Verify that the built `prefect` and `prefect-client` versions are the same
+        run: |
+          if [ "${{ steps.prefect_version.outputs.prefect_version }}" != "${{ steps.prefect_client_version.outputs.prefect_client_version }}" ]; then
+            echo "The built versions of prefect and prefect-client are not the same."
+            exit 1
+          fi
 
       - name: Run the smoke test flow again with prefect and prefect-client installed
         run: python client/client_flow.py
diff --git a/client/setup.py b/client/setup.py
index 8a55783fe15d..9eb0ddebf7c4 100644
--- a/client/setup.py
+++ b/client/setup.py
@@ -3,10 +3,6 @@
 
 install_requires = open("requirements-client.txt").read().strip().split("\n")
 
-# grab and use the first three version digits (the generated tag)
-_version = versioneer.get_version().split(".")
-client_version = ".".join(_version[:3]).split("+")[0]
-
 setup(
     # Package metadata
     name="prefect-client",
@@ -23,7 +19,8 @@
     long_description=open("README.md").read(),
     long_description_content_type="text/markdown",
     # Versioning
-    version=client_version,
+    version=versioneer.get_version(),
+    cmdclass=versioneer.get_cmdclass(),
     # Package setup
     packages=find_packages(where="src"),
     package_dir={"": "src"},
diff --git a/setup.cfg b/setup.cfg
index be0b6511b648..da3ce4accb64 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -84,7 +84,7 @@ ignore_missing_imports = True
 
 [versioneer]
 VCS = git
-style = pep440
+style = pep440-pre
 versionfile_source = src/prefect/_version.py
 versionfile_build = prefect/_version.py
 version_regex = ^(\d+\.\d+\.\d+(?:[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*)?)$
diff --git a/src/prefect/_version.py b/src/prefect/_version.py
index ec20917e8f2b..74560778d3d6 100644
--- a/src/prefect/_version.py
+++ b/src/prefect/_version.py
@@ -49,7 +49,7 @@ def get_config() -> VersioneerConfig:
     # _version.py
     cfg = VersioneerConfig()
     cfg.VCS = "git"
-    cfg.style = "pep440"
+    cfg.style = "pep440-pre"
     cfg.tag_prefix = ""
     cfg.parentdir_prefix = ""
     cfg.versionfile_source = "src/prefect/_version.py"
diff --git a/src/prefect/utilities/dockerutils.py b/src/prefect/utilities/dockerutils.py
index 16663e3ec7ac..721b93eee4bf 100644
--- a/src/prefect/utilities/dockerutils.py
+++ b/src/prefect/utilities/dockerutils.py
@@ -22,6 +22,7 @@
 from urllib.parse import urlsplit
 
 import pendulum
+from packaging.version import Version
 from typing_extensions import Self
 
 import prefect
@@ -55,8 +56,8 @@ def get_prefect_image_name(
             minor level e.g. '3.9'.
         flavor: An optional alternative image flavor to build, like 'conda'
     """
-    parsed_version = (prefect_version or prefect.__version__).split("+")
-    is_prod_build = len(parsed_version) == 1
+    parsed_version = Version(prefect_version or prefect.__version__)
+    is_prod_build = parsed_version.post is None
     prefect_version = (
         parsed_version[0]
         if is_prod_build


Code review comments:
diff_hunk: @@ -56,6 +58,10 @@ jobs:
         env:
           TMPDIR: ${{ env.TMPDIR }}
 
+      - name: (DEBUG) Print versioneer output
+        run: python -c "import versioneer; print(versioneer.get_version())"
+        working-directory: ${{ env.TMPDIR }}
path: .github/workflows/prefect-client.yaml
text: @user1:
do we want to keep this?

@author:
Nah I'll get rid of it