From ed3c4fd77f3c491674a289c58531d3ebe56f16ba Mon Sep 17 00:00:00 2001 From: Lance Drane Date: Fri, 7 Jun 2024 16:22:39 -0400 Subject: [PATCH] add publish CD job and allow for prerelease versions Signed-off-by: Lance Drane --- .github/workflows/publish.yml | 33 +++++++++++++++++++ src/intersect_sdk/__init__.py | 3 +- src/intersect_sdk/_internal/messages/event.py | 4 +-- .../_internal/messages/lifecycle.py | 4 +-- .../_internal/messages/userspace.py | 4 +-- src/intersect_sdk/_internal/schema.py | 4 +-- src/intersect_sdk/_internal/version.py | 18 ++++++++++ .../_internal/version_resolver.py | 4 +-- src/intersect_sdk/service.py | 4 +-- src/intersect_sdk/version.py | 10 ++++-- tests/unit/test_lifecycle_message.py | 4 +-- tests/unit/test_userspace_message.py | 4 +-- tests/unit/test_version_resolver.py | 4 +-- 13 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 src/intersect_sdk/_internal/version.py diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..c23afda --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,33 @@ +name: Release to PyPi + +on: + push: + tags: "v[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +permissions: + contents: write + id-token: write + +jobs: + build: + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: "3.8" + cache: true + - name: Publish package to PyPI + env: + PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: pdm publish --username __token__ --repository pypi + - name: upload to github release + uses: softprops/action-gh-release@v2 + with: + files: | + dist/* + # assume any version with "a" (gets "ALPHA") or "b" (gets BETA) will be a prerelease + prerelease: ${{ contains(github.ref, 'a') || contains(github.ref, 'b') }} diff --git a/src/intersect_sdk/__init__.py b/src/intersect_sdk/__init__.py index 769c532..fd43448 100644 --- a/src/intersect_sdk/__init__.py +++ b/src/intersect_sdk/__init__.py @@ -34,7 +34,7 @@ intersect_message, intersect_status, ) -from .version import __version__, version_info +from .version import __version__, version_info, version_string __all__ = [ 'IntersectDataHandler', @@ -61,4 +61,5 @@ 'DataStoreConfigMap', '__version__', 'version_info', + 'version_string', ] diff --git a/src/intersect_sdk/_internal/messages/event.py b/src/intersect_sdk/_internal/messages/event.py index 54d8458..19a49ad 100644 --- a/src/intersect_sdk/_internal/messages/event.py +++ b/src/intersect_sdk/_internal/messages/event.py @@ -16,7 +16,7 @@ from ...constants import SYSTEM_OF_SYSTEM_REGEX from ...core_definitions import IntersectDataHandler, IntersectMimeType -from ...version import __version__ +from ...version import version_string from ..data_plane.minio_utils import MinioPayload # TODO - another property we should consider is an optional max_wait_time for events which are fired from functions. @@ -139,7 +139,7 @@ def create_event_message( headers=EventMessageHeaders( source=source, created_at=datetime.datetime.now(tz=datetime.timezone.utc), - sdk_version=__version__, + sdk_version=version_string, event_name=event_name, data_handler=data_handler, ), diff --git a/src/intersect_sdk/_internal/messages/lifecycle.py b/src/intersect_sdk/_internal/messages/lifecycle.py index dff5151..6d15572 100644 --- a/src/intersect_sdk/_internal/messages/lifecycle.py +++ b/src/intersect_sdk/_internal/messages/lifecycle.py @@ -16,7 +16,7 @@ from typing_extensions import Annotated, TypedDict from ...constants import SYSTEM_OF_SYSTEM_REGEX -from ...version import __version__ +from ...version import version_string class LifecycleType(IntEnum): @@ -155,7 +155,7 @@ def create_lifecycle_message( source=source, destination=destination, created_at=datetime.datetime.now(tz=datetime.timezone.utc), - sdk_version=__version__, + sdk_version=version_string, lifecycle_type=lifecycle_type, ), payload=payload, diff --git a/src/intersect_sdk/_internal/messages/userspace.py b/src/intersect_sdk/_internal/messages/userspace.py index 9ffb228..e87f6a6 100644 --- a/src/intersect_sdk/_internal/messages/userspace.py +++ b/src/intersect_sdk/_internal/messages/userspace.py @@ -18,7 +18,7 @@ from ...constants import SYSTEM_OF_SYSTEM_REGEX from ...core_definitions import IntersectDataHandler, IntersectMimeType -from ...version import __version__ +from ...version import version_string from ..data_plane.minio_utils import MinioPayload @@ -152,7 +152,7 @@ def create_userspace_message( headers=UserspaceMessageHeader( source=source, destination=destination, - sdk_version=__version__, + sdk_version=version_string, created_at=datetime.datetime.now(tz=datetime.timezone.utc), data_handler=data_handler, has_error=has_error, diff --git a/src/intersect_sdk/_internal/schema.py b/src/intersect_sdk/_internal/schema.py index 487cc22..63369c4 100644 --- a/src/intersect_sdk/_internal/schema.py +++ b/src/intersect_sdk/_internal/schema.py @@ -16,7 +16,7 @@ from pydantic import PydanticUserError, TypeAdapter from typing_extensions import TypeAliasType -from ..version import __version__ +from ..version import version_string from .constants import ( BASE_EVENT_ATTR, BASE_RESPONSE_ATTR, @@ -513,7 +513,7 @@ def get_schema_and_functions_from_capability_implementation( asyncapi_spec = { 'asyncapi': ASYNCAPI_VERSION, - 'x-intersect-version': __version__, + 'x-intersect-version': version_string, 'info': { 'title': capability_name.hierarchy_string('.'), 'version': '0.0.0', # NOTE: this will be modified by INTERSECT CORE, users do not manage their schema versions diff --git a/src/intersect_sdk/_internal/version.py b/src/intersect_sdk/_internal/version.py new file mode 100644 index 0000000..4cc68d3 --- /dev/null +++ b/src/intersect_sdk/_internal/version.py @@ -0,0 +1,18 @@ +"""Version sanity checks to make sure that the release version is properly formatted.""" + + +def strip_version_metadata(version: str) -> str: + """Given a string, do the following. + + 1) Strip out pre-release/build-metadata from the string + 2) If the string is missing all of .., raise runtime error + + This is necessary because INTERSECT works off of a strict SemVer string and does not understand build metadata. + """ + import re + + sem_ver = re.search(r'\d+\.\d+\.\d+', version) + if sem_ver is None: + msg = 'Package version does not contain a semantic version "..", please fix this' + raise RuntimeError(msg) + return sem_ver.group() diff --git a/src/intersect_sdk/_internal/version_resolver.py b/src/intersect_sdk/_internal/version_resolver.py index 8ed0616..35a850d 100644 --- a/src/intersect_sdk/_internal/version_resolver.py +++ b/src/intersect_sdk/_internal/version_resolver.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING from ..core_definitions import IntersectDataHandler -from ..version import __version__, version_info +from ..version import version_info, version_string from .logger import logger if TYPE_CHECKING: @@ -53,6 +53,6 @@ def resolve_user_version(msg: UserspaceMessage | EventMessage) -> bool: """ return _resolve_user_version( msg=msg, - our_version=__version__, + our_version=version_string, our_version_info=version_info, ) diff --git a/src/intersect_sdk/service.py b/src/intersect_sdk/service.py index eaf10e7..9665407 100644 --- a/src/intersect_sdk/service.py +++ b/src/intersect_sdk/service.py @@ -49,7 +49,7 @@ from .capability.base import IntersectBaseCapabilityImplementation from .config.service import IntersectServiceConfig from .core_definitions import IntersectDataHandler, IntersectMimeType -from .version import __version__ +from .version import version_string if TYPE_CHECKING: from ._internal.function_metadata import FunctionMetadata @@ -361,7 +361,7 @@ def _handle_userspace_message(self, message: UserspaceMessage) -> UserspaceMessa return None if not resolve_user_version(message): return self._make_error_message( - f'SDK version incompatibility. Service version: {__version__} . Sender version: {message["headers"]["sdk_version"]}', + f'SDK version incompatibility. Service version: {version_string} . Sender version: {message["headers"]["sdk_version"]}', message, ) diff --git a/src/intersect_sdk/version.py b/src/intersect_sdk/version.py index dd12500..44fe7db 100644 --- a/src/intersect_sdk/version.py +++ b/src/intersect_sdk/version.py @@ -5,11 +5,17 @@ from __future__ import annotations +from ._internal.version import strip_version_metadata + +# may include build metadata __version__ = '0.6.1' + +version_string = strip_version_metadata(__version__) """ -Version string in the format .. . Follows semantic versioning rules. +Version string in the format .. . Follows semantic versioning rules, strips out additional build metadata. """ -version_info: tuple[int, int, int] = tuple([int(x) for x in __version__.split('.')]) # type: ignore[assignment] + +version_info: tuple[int, int, int] = tuple([int(x) for x in version_string.split('.')]) # type: ignore[assignment] """ Integer tuple in the format ,, . Follows semantic versioning rules. """ diff --git a/tests/unit/test_lifecycle_message.py b/tests/unit/test_lifecycle_message.py index ca36a84..fd2b2e6 100644 --- a/tests/unit/test_lifecycle_message.py +++ b/tests/unit/test_lifecycle_message.py @@ -6,7 +6,7 @@ import uuid import pytest -from intersect_sdk import __version__ +from intersect_sdk import version_string from intersect_sdk._internal.messages.lifecycle import ( LifecycleType, create_lifecycle_message, @@ -95,6 +95,6 @@ def test_create_lifecycle_message(): # enforce UTC assert msg['headers']['created_at'].tzinfo == datetime.timezone.utc assert msg['headers']['lifecycle_type'] == LifecycleType.SHUTDOWN - assert msg['headers']['sdk_version'] == __version__ + assert msg['headers']['sdk_version'] == version_string assert msg['headers']['source'] == 'source' assert msg['headers']['destination'] == 'destination' diff --git a/tests/unit/test_userspace_message.py b/tests/unit/test_userspace_message.py index a263efd..6302bb7 100644 --- a/tests/unit/test_userspace_message.py +++ b/tests/unit/test_userspace_message.py @@ -6,7 +6,7 @@ import uuid import pytest -from intersect_sdk import IntersectDataHandler, IntersectMimeType, __version__ +from intersect_sdk import IntersectDataHandler, IntersectMimeType, version_string from intersect_sdk._internal.messages.userspace import ( create_userspace_message, deserialize_and_validate_userspace_message, @@ -100,6 +100,6 @@ def test_create_userspace_message(): # enforce UTC assert msg['headers']['created_at'].tzinfo == datetime.timezone.utc assert msg['headers']['data_handler'] == IntersectDataHandler.MESSAGE - assert msg['headers']['sdk_version'] == __version__ + assert msg['headers']['sdk_version'] == version_string assert msg['headers']['source'] == 'source' assert msg['headers']['destination'] == 'destination' diff --git a/tests/unit/test_version_resolver.py b/tests/unit/test_version_resolver.py index f31cefe..1f90b9e 100644 --- a/tests/unit/test_version_resolver.py +++ b/tests/unit/test_version_resolver.py @@ -17,8 +17,8 @@ from intersect_sdk import ( IntersectDataHandler, IntersectMimeType, - __version__, version_info, + version_string, ) from intersect_sdk._internal.messages.userspace import UserspaceMessage, UserspaceMessageHeader from intersect_sdk._internal.version_resolver import _resolve_user_version, resolve_user_version @@ -59,7 +59,7 @@ def test_version_info(): def test_equal_version_ok(): - assert resolve_user_version(message_generator(__version__)) is True + assert resolve_user_version(message_generator(version_string)) is True def test_bugfix_up_ok():