From 05c9e5184c804732e8a432c30965a2848179d190 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Tue, 4 Nov 2025 08:37:35 +0100 Subject: [PATCH] Implemented devops decorator and a get method that retrieves object properties by a key. --- sk/devops.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/sk/devops.py b/sk/devops.py index 4898db6..538d538 100644 --- a/sk/devops.py +++ b/sk/devops.py @@ -2,12 +2,13 @@ from __future__ import annotations import requests import urllib.parse from uuid import UUID +from string import Template DEVOPS_SCOPE = "https://app.vssps.visualstudio.com/.default" DEVOPS_API_VERSION = "7.1" # Define a class decorator -def auto_properties(mapping: dict[str,str] | None = None): +def auto_properties(mapping: dict[str,str]): def make_property(name: str): private_var = f"_{name}" @@ -53,6 +54,15 @@ def auto_properties(mapping: dict[str,str] | None = None): return cls return decorator +def devops(key: str, get_url: str, list_url: str = None, params: dict = {}): + def decorator(cls): + cls.__entity_key__ = key + cls.__entity_get_url__ = get_url # Use $key in the URL + cls.__entity_list_url__ = list_url # Use $key in the URL + cls.__entity_params__ = params + return cls + return decorator + class DevOps(): """Base class for DevOps entities.""" @@ -77,7 +87,24 @@ class DevOps(): r.raise_for_status() # Ensure we raise an error for bad responses return r - def _get_entity(self, key_name: str, get_url: str, params: dict = {}) -> object: + def _get(self, key: str): + if not hasattr(self.__class__, "__entity_key__") or not hasattr(self.__class__, "__entity_get_url__"): + raise NotImplementedError("Called _get on a class that has not been decorated with @devops.") + setattr(self, f"_{self.__class__.__entity_key__}", key) # Set the entity key + # Build the URL + url = Template(self.__class__.__entity_get_url__).substitute(key=key) + # Build parameters with key substituted + params = {} + if hasattr(self.__class__, "__entity_params__"): + params = {k: Template(v).substitute(key=key) for k, v in self.__class__.__entity_params__.items()} + + # Fetch the object data from the URL + r = self._get_url_path(url, params=params) + + # Populate attributes + self.from_json(r.json()) + + def _get_entity(self, key_name: str, get_url: str, params: dict = {}): """ Each entity class can use this method to populate its attributes, by defining its own _get method that calls this one with the key name, @@ -129,6 +156,7 @@ class Organization(DevOps): "url": "url", "description": "description" }) +@devops("id", "_apis/projects/$key", "_apis/projects") class Project(DevOps): def _get(self): self._get_entity(