diff --git a/CHANGES.rst b/CHANGES.rst index 12ac77a..4f9500c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,17 @@ details, see the commit logs at https://github.com/girder/slicer_package_manager Next Release ============ +New Features +------------ + +* Add support for uploading extension packages specifying the ``tier`` metadata. The ``tier`` + metadata can be set as an integer value of ``1``, ``3`` or ``5``. + +* Support listing extension with the ``tier`` and introdce the ``tier_compare`` parameter, which can + be set as ``exact`` or ``lte`` (less than or equal to). + By default, the ``tier_compare`` parameter is set to ``lte`` and extensions with their tier less or + equal to ``5`` are listed. + 0.9.0 ===== diff --git a/pyproject.toml b/pyproject.toml index 1d32b37..59e432f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,3 +149,6 @@ ignore-variadic-names = true [tool.ruff.per-file-ignores] "python_client/slicer_package_manager_client/__init__.py" = ["A002"] # Argument `all` is shadowing a python builtin + +[tool.ruff.pylint] +max-statements = 60 \ No newline at end of file diff --git a/python_client/slicer_package_manager_client/__init__.py b/python_client/slicer_package_manager_client/__init__.py index 1e4386c..b246de4 100644 --- a/python_client/slicer_package_manager_client/__init__.py +++ b/python_client/slicer_package_manager_client/__init__.py @@ -217,7 +217,7 @@ def deleteDraftRelease(self, app_name, revision, coll_id=None): def uploadExtension(self, filepath, app_name, ext_os, arch, name, repo_type, repo_url, revision, app_revision, desc='', icon_url='', - category=None, homepage='', screenshots=None, contributors=None, + category=None, tier=None, homepage='', screenshots=None, contributors=None, dependency=None, coll_id=None, force=False): """ Upload an extension by providing a path to the file. It can also be used to update an @@ -236,6 +236,7 @@ def uploadExtension(self, filepath, app_name, ext_os, arch, name, repo_type, rep :param desc: The description of the extension :param icon_url: Url of the extension's logo :param category: Category of the extension + :param tier: Tier of the extension. :param homepage: Url of the extension's homepage :param screenshots: Space-separate list of URLs of screenshots for the extension. :param contributors: List of contributors of the extension. @@ -268,6 +269,7 @@ def _displayProgress(*args, **kwargs): 'description': desc, 'icon_url': icon_url, 'category': category, + 'tier': tier, 'homepage': homepage, 'screenshots': screenshots, 'contributors': contributors, @@ -313,6 +315,7 @@ def _displayProgress(*args, **kwargs): 'description': desc, 'icon_url': icon_url, 'category': category, + 'tier': tier, 'homepage': homepage, 'screenshots': screenshots, 'contributors': contributors, diff --git a/python_client/slicer_package_manager_client/cli.py b/python_client/slicer_package_manager_client/cli.py index 4555927..76e447f 100644 --- a/python_client/slicer_package_manager_client/cli.py +++ b/python_client/slicer_package_manager_client/cli.py @@ -417,6 +417,9 @@ def _cli_deleteDraftRelease(sc: SlicerPackageClient, *args, **kwargs): @click.option('--category', default=None, help='Category of the extension', cls=_AdvancedOption) +@click.option('--tier', default=5, + help='Tier of the extension', + cls=_AdvancedOption) @click.option('--homepage', default='', help='Url of the extension homepage', cls=_AdvancedOption) diff --git a/slicer_package_manager/api/app.py b/slicer_package_manager/api/app.py index a75a2e8..784ad75 100644 --- a/slicer_package_manager/api/app.py +++ b/slicer_package_manager/api/app.py @@ -434,12 +434,15 @@ def deleteReleaseByIdOrName(self, app_folder, release_id_or_name, progress): .param('app_revision', 'The revision of the application.', required=False) .param('baseName', 'The baseName of the extension', required=False) .param('q', 'The search query.', required=False) + .param('tier', 'Tier of the extension.', required=False, enum=[1, 3, 5]) + .param('tier_compare', 'Comparison type for the tier.', + required=False, enum=['exact', 'lte'], default='lte') .pagingParams(defaultSort='created', defaultSortDir=SortDir.DESCENDING) .errorResponse(), ) @access.public(scope=TokenScope.DATA_READ) def getExtensions(self, app_id, extension_name, release_id, extension_id, os, arch, - app_revision, baseName, q, limit, sort, offset=0): + app_revision, baseName, q, tier, tier_compare, limit, sort, offset=0): """ Get a list of extension which is filtered by some optional parameters. If the ``release_id`` provided correspond to the draft release, then you must provide the app_revision to use @@ -454,6 +457,8 @@ def getExtensions(self, app_id, extension_name, release_id, extension_id, os, ar :param app_revision: The revision of the application :param baseName: The baseName of the extension :param q: Text expected to be found in the extension name or description + :param tier: Tier of the extension. + :param tier_compare: Comparison type for the tier specified as "exact" or "lte" (less than or equal to). :return: The list of extensions """ user = self.getCurrentUser() @@ -484,6 +489,12 @@ def getExtensions(self, app_id, extension_name, release_id, extension_id, os, ar {'meta.baseName': {'$regex': escaped_query, '$options': 'i'}}, {'meta.description': {'$regex': escaped_query, '$options': 'i'}}, ] + if tier: + if tier_compare == 'lte': + filters['meta.tier'] = {'$lte': tier} + else: + # Provide an exact match base on tier + filters['meta.tier'] = tier if ObjectId.is_valid(release_id): release = self._model.load(release_id, user=user, level=AccessType.READ) if release['name'] == constants.DRAFT_RELEASE_NAME: @@ -563,7 +574,7 @@ def getExtensions(self, app_id, extension_name, release_id, extension_id, os, ar .param('category', 'Category under which to place the extension. Subcategories should be ' 'delimited by character. If none is passed, will render under ' 'the Miscellaneous category..', required=False) - + .param('tier', 'Tier of the extension.', required=False, dataType='integer') .param('homepage', 'The url of the extension homepage.', required=False) .param('screenshots', 'Space-separate list of URLs of screenshots for the extension.', required=False) @@ -579,7 +590,7 @@ def getExtensions(self, app_id, extension_name, release_id, extension_id, os, ar @access.user(scope=TokenScope.DATA_WRITE) def createOrUpdateExtension(self, app_id, os, arch, baseName, repository_type, repository_url, revision, app_revision, description, - icon_url, development_status, category, enabled, homepage, + icon_url, development_status, category, tier, enabled, homepage, screenshots, contributors, dependency, license): """ Create or update an extension item. @@ -600,6 +611,7 @@ def createOrUpdateExtension(self, app_id, os, arch, baseName, repository_type, r :param revision: The revision of the extension. :param app_revision: The revision of the application. :param description: The description of the extension. + :param tier: Tier of the extension. :return: The created/updated extension. """ creator = self.getCurrentUser() @@ -645,6 +657,8 @@ def createOrUpdateExtension(self, app_id, os, arch, baseName, repository_type, r params['development_status'] = development_status if category: params['category'] = category + if tier: + params['tier'] = tier if enabled: params['enabled'] = enabled if homepage: diff --git a/slicer_package_manager/models/extension.py b/slicer_package_manager/models/extension.py index b121e82..6f4adaf 100644 --- a/slicer_package_manager/models/extension.py +++ b/slicer_package_manager/models/extension.py @@ -95,6 +95,7 @@ def validate(self, doc): 'icon_url', 'development_status', 'category', + 'tier', 'enabled', 'homepage', 'screenshots', @@ -107,7 +108,13 @@ def validate(self, doc): raise ValidationException(msg) specs = [] for meta in extra_params: - if meta == 'enabled': + if meta == 'tier': + specs.append({ + 'name': meta, + 'type': int, + 'exception_msg': f'Extension field "{meta}" must be an integer.', + }) + elif meta == 'enabled': specs.append({ 'name': meta, 'type': bool,