Skip to content

Commit 2a0cd86

Browse files
authored
Merge pull request #130 from lsst-sqre/tickets/DM-44453
DM-44453: Fix handling of release versions in v2 API.
2 parents a027c32 + 16c7b0d commit 2a0cd86

File tree

10 files changed

+133
-17
lines changed

10 files changed

+133
-17
lines changed

.github/workflows/ci.yaml

+9-10
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,22 @@ jobs:
4141
db:
4242
- sqlite
4343
- postgres
44-
- mysql
44+
# - mysql
4545

4646
steps:
4747
- uses: actions/checkout@v3
4848

49+
- name: Install build tools
50+
run: sudo apt-get install build-essential
51+
4952
- name: Set up Python
5053
uses: actions/setup-python@v4
5154
with:
5255
python-version: ${{ matrix.python }}
5356

5457
- name: Install tox
5558
run: |
59+
pip install 'requests<2.32.0'
5660
pip install tox
5761
pip install --pre tox-docker
5862
@@ -73,11 +77,13 @@ jobs:
7377
LTD_KEEPER_TEST_AWS_ID: ${{ secrets.LTD_KEEPER_TEST_AWS_ID }}
7478
LTD_KEEPER_TEST_AWS_SECRET: ${{ secrets.LTD_KEEPER_TEST_AWS_SECRET }}
7579
LTD_KEEPER_TEST_BUCKET: ${{ secrets.LTD_KEEPER_TEST_BUCKET }}
76-
run: tox -e typing,${{matrix.db}},coverage-report # run tox using Python in path
80+
# run: tox -e typing,${{matrix.db}},coverage-report # run tox using Python in path
81+
run: tox -e ${{matrix.db}},coverage-report # run tox using Python in path
7782

7883
- name: Run tox without external services
7984
if: ${{ !(matrix.python != '3.10' && matrix.db != 'postgres') }}
80-
run: tox -e typing,${{matrix.db}},coverage-report # run tox using Python in path
85+
# run: tox -e typing,${{matrix.db}},coverage-report # run tox using Python in path
86+
run: tox -e ${{matrix.db}},coverage-report # run tox using Python in path
8187

8288
docs:
8389
runs-on: ubuntu-latest
@@ -141,12 +147,6 @@ jobs:
141147
- name: Set up Docker Buildx
142148
uses: docker/setup-buildx-action@v2
143149

144-
- name: Log in to Docker Hub
145-
uses: docker/login-action@v2
146-
with:
147-
username: ${{ secrets.DOCKER_USERNAME }}
148-
password: ${{ secrets.DOCKER_TOKEN }}
149-
150150
- name: Log in to GitHub Container Registry
151151
uses: docker/login-action@v2
152152
with:
@@ -160,7 +160,6 @@ jobs:
160160
context: .
161161
push: true
162162
tags: |
163-
lsstsqre/ltdkeeper:${{ steps.vars.outputs.tag }}
164163
ghcr.io/lsst-sqre/ltd-keeper:${{ steps.vars.outputs.tag }}
165164
cache-from: type=gha
166165
cache-to: type=gha,mode=max

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ __pycache__/
77
*.so
88

99
# Distribution / packaging
10+
venv
11+
.venv
1012
.Python
1113
env/
1214
build/

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: check-json
1010

1111
- repo: https://github.com/PyCQA/isort
12-
rev: 5.10.1
12+
rev: 5.13.2
1313
hooks:
1414
- id: isort
1515
additional_dependencies:
@@ -20,7 +20,7 @@ repos:
2020
hooks:
2121
- id: black
2222

23-
- repo: https://gitlab.com/pycqa/flake8
23+
- repo: https://github.com/pycqa/flake8
2424
rev: 4.0.1
2525
hooks:
2626
- id: flake8

keeper/editiontracking/lsstdocmode.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222

2323
# The RFC-405/LPM-51 format for LSST semantic document versions.
2424
# v<minor>.<major>
25-
LSST_DOC_V_TAG = re.compile(r"^v(?P<major>[\d]+)\.(?P<minor>[\d]+)$")
25+
LSST_DOC_V_TAG = re.compile(
26+
r"^v?(?P<major>[\d]+)\.(?P<minor>[\d]+)(\.(?P<patch>[\d]+))?$"
27+
)
2628

2729

2830
class LsstDocTrackingMode(TrackingModeBase):
2931
"""LSST document-specific tracking mode where an edition publishes the
3032
most recent ``vN.M`` tag.
33+
34+
Semantic versions are also supported: ``N.M.P`` or ``vN.M.P``.
3135
"""
3236

3337
@property
@@ -100,7 +104,10 @@ def __init__(self, version_str: str) -> None:
100104
raise ValueError(
101105
"{:r} is not a LSST document version tag".format(version_str)
102106
)
103-
self.version = (int(match.group("major")), int(match.group("minor")))
107+
major = int(match.group("major"))
108+
minor = int(match.group("minor"))
109+
patch = int(match.group("patch") or 0)
110+
self.version = (major, minor, patch)
104111

105112
@property
106113
def major(self) -> int:
@@ -110,6 +117,10 @@ def major(self) -> int:
110117
def minor(self) -> int:
111118
return self.version[1]
112119

120+
@property
121+
def patch(self) -> int:
122+
return self.version[2]
123+
113124
def __repr__(self) -> str:
114125
return "LsstDocVersion({:r})".format(self.version_str)
115126

keeper/models.py

+25
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,31 @@ def mode_name(self) -> str:
10121012
else:
10131013
return self.default_mode_name
10141014

1015+
def set_kind(self, kind: str) -> None:
1016+
"""Set the edition kind.
1017+
1018+
Parameters
1019+
----------
1020+
kind : `str`
1021+
Kind identifier. Validated to be one defined in `EditionKind`.
1022+
1023+
Raises
1024+
------
1025+
ValidationError
1026+
Raised if `kind` is unknown.
1027+
"""
1028+
self.kind = EditionKind[kind]
1029+
1030+
@property
1031+
def kind_name(self) -> str:
1032+
"""Name of the kind (`str`).
1033+
1034+
See also
1035+
--------
1036+
EditionKind
1037+
"""
1038+
return self.kind.name
1039+
10151040
def update_slug(self, new_slug: str) -> None:
10161041
"""Update the edition's slug by migrating files on S3.
10171042

keeper/services/createedition.py

+34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import re
34
import uuid
45
from typing import TYPE_CHECKING, Optional
56

@@ -21,6 +22,7 @@ def create_edition(
2122
autoincrement_slug: Optional[bool] = False,
2223
tracked_ref: Optional[str] = "main",
2324
build: Optional[Build] = None,
25+
kind: Optional[str] = None,
2426
) -> Edition:
2527
"""Create a new edition.
2628
@@ -50,6 +52,8 @@ def create_edition(
5052
is ``"git_refs"`` or ``"git_ref"``.
5153
build : Build, optional
5254
The build to initially publish with this edition.
55+
kind : str, optional
56+
The kind of the edition.
5357
5458
Returns
5559
-------
@@ -83,6 +87,16 @@ def create_edition(
8387
edition.tracked_ref = tracked_ref
8488
edition.tracked_refs = [tracked_ref]
8589

90+
if edition.slug == "__main":
91+
# Always mark the default edition as the main edition
92+
edition.set_kind("main")
93+
elif kind is not None:
94+
# Manually set the edition kind
95+
edition.set_kind(kind)
96+
elif tracked_ref is not None:
97+
# Set the EditionKind based on the tracked_ref value
98+
edition.set_kind(determine_edition_kind(tracked_ref))
99+
86100
db.session.add(edition)
87101
db.session.commit()
88102

@@ -92,3 +106,23 @@ def create_edition(
92106
request_dashboard_build(product)
93107

94108
return edition
109+
110+
111+
SEMVER_PATTERN = re.compile(
112+
r"^v?(?P<major>[\d]+)(\.(?P<minor>[\d]+)(\.(?P<patch>[\d]+))?)?$"
113+
)
114+
115+
116+
def determine_edition_kind(git_ref: str) -> str:
117+
"""Determine the kind of edition based on the git ref."""
118+
match = SEMVER_PATTERN.match(git_ref)
119+
if match is None:
120+
return "draft"
121+
122+
if match.group("patch") is not None and match.group("minor") is not None:
123+
return "release"
124+
125+
if match.group("minor") is not None and match.group("patch") is None:
126+
return "minor"
127+
128+
return "major"

keeper/services/updateedition.py

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def update_edition(
2727
tracking_mode: Optional[str] = None,
2828
tracked_ref: Optional[str] = None,
2929
pending_rebuild: Optional[bool] = None,
30+
kind: Optional[str] = None,
3031
) -> Edition:
3132
"""Update the metadata of an existing edititon or to point at a new
3233
build.
@@ -63,6 +64,9 @@ def update_edition(
6364
)
6465
edition.pending_rebuild = pending_rebuild
6566

67+
if kind is not None:
68+
edition.set_kind(kind)
69+
6670
db.session.add(edition)
6771
db.session.commit()
6872

keeper/v2api/_models.py

+33-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from keeper.editiontracking import EditionTrackingModes
1212
from keeper.exceptions import ValidationError
13-
from keeper.models import OrganizationLayoutMode
13+
from keeper.models import EditionKind, OrganizationLayoutMode
1414
from keeper.utils import (
1515
format_utc_datetime,
1616
validate_path_slug,
@@ -119,7 +119,6 @@ def from_organization(cls, org: Organization) -> OrganizationResponse:
119119

120120

121121
class LayoutEnum(str, Enum):
122-
123122
subdomain = "subdomain"
124123

125124
path = "path"
@@ -576,6 +575,9 @@ class EditionResponse(BaseModel):
576575
mode: str
577576
"""The edition tracking mode."""
578577

578+
kind: str
579+
"""The edition kind."""
580+
579581
@classmethod
580582
def from_edition(
581583
cls,
@@ -609,6 +611,7 @@ def from_edition(
609611
"date_rebuilt": edition.date_rebuilt,
610612
"date_ended": edition.date_ended,
611613
"mode": edition.mode_name,
614+
"kind": edition.kind_name,
612615
"tracked_ref": tracked_ref,
613616
"pending_rebuild": edition.pending_rebuild,
614617
"surrogate_key": edition.surrogate_key,
@@ -661,6 +664,9 @@ class EditionPostRequest(BaseModel):
661664
mode: str = "git_refs"
662665
"""Tracking mode."""
663666

667+
kind: Optional[str] = None
668+
"""The edition kind."""
669+
664670
tracked_ref: Optional[str] = None
665671
"""Git ref being tracked if mode is ``git_ref``."""
666672

@@ -708,6 +714,17 @@ def check_tracked_refs(
708714
raise ValueError('tracked_ref must be set if mode is "git_ref"')
709715
return v
710716

717+
@validator("kind")
718+
def check_kind(cls, v: Optional[str]) -> Optional[str]:
719+
if v is None:
720+
return None
721+
722+
# Get all known kinds from the EditionKind enum
723+
kind_names = [kind.name for kind in EditionKind]
724+
if v not in kind_names:
725+
raise ValueError(f"Kind {v!r} is not known.")
726+
return v
727+
711728

712729
class EditionPatchRequest(BaseModel):
713730
"""The model for a PATCH /editions/:id request."""
@@ -731,6 +748,9 @@ class EditionPatchRequest(BaseModel):
731748
mode: Optional[str] = None
732749
"""The edition tracking mode."""
733750

751+
kind: Optional[str] = None
752+
"""The edition kind."""
753+
734754
build_url: Optional[HttpUrl] = None
735755
"""URL of the build to initially publish with the edition, if available.
736756
"""
@@ -756,6 +776,17 @@ def check_mode(cls, v: Optional[str]) -> Optional[str]:
756776
raise ValueError(f"Tracking mode {v!r} is not known.")
757777
return v
758778

779+
@validator("kind")
780+
def check_kind(cls, v: Optional[str]) -> Optional[str]:
781+
if v is None:
782+
return None
783+
784+
# Get all known kinds from the EditionKind enum
785+
kind_names = [kind.name for kind in EditionKind]
786+
if v not in kind_names:
787+
raise ValueError(f"Kind {v!r} is not known.")
788+
return v
789+
759790

760791
class QueuedResponse(BaseModel):
761792
"""Response that contains only a URL for the background task's status."""

keeper/v2api/editions.py

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def post_edition(org: str, project: str) -> Tuple[str, int, Dict[str, str]]:
8888
slug=request_data.slug,
8989
autoincrement_slug=request_data.autoincrement,
9090
tracked_ref=request_data.tracked_ref,
91+
kind=request_data.kind,
9192
build=build,
9293
)
9394
except Exception:
@@ -130,6 +131,7 @@ def patch_edition(
130131
slug=request_data.slug,
131132
tracking_mode=request_data.mode,
132133
tracked_ref=request_data.tracked_ref,
134+
kind=request_data.kind,
133135
pending_rebuild=request_data.pending_rebuild,
134136
)
135137
except Exception:

tests/test_lsstdocmode.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ def test_lsst_doc_tag_order() -> None:
2424
assert v311 > v39
2525

2626

27+
def test_semver_tag() -> None:
28+
version = LsstDocVersionTag("1.2.3")
29+
30+
assert version.major == 1
31+
assert version.minor == 2
32+
assert version.patch == 3
33+
34+
2735
def test_invalid_lsst_doc_tag() -> None:
2836
with pytest.raises(ValueError):
29-
LsstDocVersionTag("1.2")
37+
LsstDocVersionTag("1.2rc1")

0 commit comments

Comments
 (0)