Skip to content

Commit b49aae0

Browse files
committed
add stable repository element filtering
1 parent 9baeb15 commit b49aae0

9 files changed

Lines changed: 689 additions & 23 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,4 @@ docs/source/*.png
139139
pgdata/
140140

141141
.tasks
142+
graphify-out

docs/openapi/openapi_user.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28617,6 +28617,14 @@ paths:
2861728617
example: 00000000-0000-0000-0000-000000000000
2861828618
format: uuid
2861928619
nullable: true
28620+
- name: latest
28621+
in: query
28622+
schema:
28623+
type: string
28624+
enum:
28625+
- "true"
28626+
example: "true"
28627+
description: Return only the latest version for each unique element name
2862028628
responses:
2862128629
'200':
2862228630
description: RepoElement_Filter

exordos_core/repo/dm/models.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import typing as tp
2121
from urllib.parse import urljoin
2222

23+
from packaging import version as packaging_version
2324
from restalchemy.dm import filters as ra_filters
2425
from restalchemy.dm import models
2526
from restalchemy.dm import properties
@@ -28,6 +29,7 @@
2829
from restalchemy.dm import types_dynamic
2930
from restalchemy.storage.sql import orm
3031

32+
from exordos_core.common import exceptions as common_exc
3133
from exordos_core.common import utils
3234
from exordos_core.repo import constants as rc
3335

@@ -37,6 +39,13 @@
3739
from exordos_core.repo.drivers.base import AbstractProxyRepoDriver
3840

3941

42+
def is_stable_version(version: str) -> bool:
43+
try:
44+
return not packaging_version.parse(version).is_prerelease
45+
except packaging_version.InvalidVersion:
46+
return False
47+
48+
4049
class SyncMode(str, enum.Enum):
4150
COPY = "copy"
4251
LAZY = "lazy"
@@ -213,10 +222,10 @@ def load_driver(self) -> "AbstractProxyRepoDriver":
213222
instantiate them with the current repository. If a driver is
214223
successfully loaded, it is stored in a cache for faster access.
215224
216-
If no driver is found, a ValueError is raised.
225+
If no driver is found, a ValidateException is raised.
217226
218227
:return: The loaded driver instance
219-
:raises ValueError: If no driver is found
228+
:raises ValidateException: If no driver is found
220229
"""
221230
driver_key = str(self.driver_spec)
222231

@@ -234,7 +243,9 @@ def load_driver(self) -> "AbstractProxyRepoDriver":
234243
# Just try another driver
235244
pass
236245

237-
raise ValueError(f"Driver for spec '{self.driver_spec}' not found")
246+
raise common_exc.ValidateException(
247+
err=f"Driver for spec '{self.driver_spec}' not found"
248+
)
238249

239250
def iter_elements_in_inventory(
240251
self, inventory: dict | None = None
@@ -309,13 +320,15 @@ def upload(
309320
Created RepoElement instance
310321
311322
Raises:
312-
ValueError: If upload is not supported by driver
323+
ValidateException: If upload is not supported by driver
313324
"""
314325
driver = self.load_driver()
315326

316327
# Check if driver supports upload
317328
if not driver.can_upload_element(element_name, element_version):
318-
raise ValueError("Upload is not supported by this repository driver")
329+
raise common_exc.ValidateException(
330+
err="Upload is not supported by this repository driver"
331+
)
319332

320333
# Create element
321334
element = RepoElement(
@@ -418,6 +431,10 @@ class RepoElement(
418431
default=None,
419432
)
420433

434+
@property
435+
def is_stable(self) -> bool:
436+
return is_stable_version(self.version)
437+
421438
@property
422439
def dependencies(self) -> dict[str, dict[str, str]]:
423440
"""Compute dependencies from manifest requirements.
@@ -466,7 +483,7 @@ def dependencies(self) -> dict[str, dict[str, str]]:
466483

467484
def install(self) -> "RepoElement":
468485
if self.installation_state != RepoElementInstallationState.UNINSTALLED:
469-
raise ValueError("Element must be uninstalled")
486+
raise common_exc.ValidateException(err="Element must be uninstalled")
470487

471488
# Check there is no installed element with the same name
472489
existing = RepoElement.objects.get_all(
@@ -478,15 +495,17 @@ def install(self) -> "RepoElement":
478495
}
479496
)
480497
if existing:
481-
raise ValueError("Element with the same name is already installed")
498+
raise common_exc.ValidateException(
499+
err="Element with the same name is already installed"
500+
)
482501

483502
self.installation_state = RepoElementInstallationState.INSTALLED.value
484503
self.update()
485504
return self
486505

487506
def uninstall(self) -> "RepoElement":
488507
if self.installation_state != RepoElementInstallationState.INSTALLED:
489-
raise ValueError("Element must be installed")
508+
raise common_exc.ValidateException(err="Element must be installed")
490509

491510
# Check that no other elements depend on this one. The dependency
492511
# bindings table records transitive dependencies, so if any record
@@ -496,8 +515,8 @@ def uninstall(self) -> "RepoElement":
496515
filters={"depends_on": ra_filters.EQ(self.uuid)}
497516
)
498517
if dependents:
499-
raise ValueError(
500-
"Element cannot be uninstalled: other elements depend on it"
518+
raise common_exc.ValidateException(
519+
err="Element cannot be uninstalled: other elements depend on it"
501520
)
502521

503522
self.installation_state = RepoElementInstallationState.UNINSTALLED.value
@@ -516,7 +535,9 @@ def uninstall(self) -> "RepoElement":
516535

517536
def upgrade(self, target: str) -> "RepoElement":
518537
if self.element is None:
519-
raise ValueError("Element must be installed to upgrade")
538+
raise common_exc.ValidateException(
539+
err="Element must be installed to upgrade"
540+
)
520541

521542
target_element = RepoElement.objects.get_one(
522543
filters={
@@ -528,7 +549,7 @@ def upgrade(self, target: str) -> "RepoElement":
528549
target_element.installation_state
529550
!= RepoElementInstallationState.UNINSTALLED
530551
):
531-
raise ValueError("Target element must be uninstalled")
552+
raise common_exc.ValidateException(err="Target element must be uninstalled")
532553
runtime_element = self.element
533554
self.installation_state = RepoElementInstallationState.UNINSTALLED.value
534555
self.update()
@@ -545,20 +566,20 @@ def edit(self, manifest: dict) -> "RepoElement":
545566
manifest: New manifest dict
546567
547568
Raises:
548-
ValueError: If manifest name or version does not match element name/version
569+
ValidateException: If manifest name or version does not match element name/version
549570
"""
550571
# Validate that name and version in manifest match the element
551572
manifest_name = manifest.get("name")
552573
manifest_version = manifest.get("version")
553574

554575
if manifest_name != self.name:
555-
raise ValueError(
556-
f"Manifest name '{manifest_name}' does not match element name '{self.name}'"
576+
raise common_exc.ValidateException(
577+
err=f"Manifest name '{manifest_name}' does not match element name '{self.name}'"
557578
)
558579

559580
if manifest_version != self.version:
560-
raise ValueError(
561-
f"Manifest version '{manifest_version}' does not match element version '{self.version}'"
581+
raise common_exc.ValidateException(
582+
err=f"Manifest version '{manifest_version}' does not match element version '{self.version}'"
562583
)
563584

564585
self.manifest = manifest
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2026 Genesis Corporation.
2+
#
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.

0 commit comments

Comments
 (0)