Skip to content

MPT-19892: add Extension base service for /public/v1/extensibility/extensions#276

Merged
albertsola merged 1 commit intomainfrom
MPT-19892/extension-base-service
Apr 2, 2026
Merged

MPT-19892: add Extension base service for /public/v1/extensibility/extensions#276
albertsola merged 1 commit intomainfrom
MPT-19892/extension-base-service

Conversation

@albertsola
Copy link
Copy Markdown
Contributor

@albertsola albertsola commented Apr 2, 2026

Summary

Implements the Extension base service for the /public/v1/extensibility/extensions endpoint.

Changes

New source files

  • mpt_api_client/resources/extensibility/mixins/extension_mixin.pyExtensionMixin / AsyncExtensionMixin with publish, unpublish, regenerate, token, download_icon
  • mpt_api_client/resources/extensibility/extensions.pyExtension model + ExtensionsService / AsyncExtensionsService
  • mpt_api_client/resources/extensibility/extensibility.pyExtensibility / AsyncExtensibility accessor

Modified files

  • mpt_api_client/resources/__init__.py — exports Extensibility, AsyncExtensibility
  • mpt_api_client/mpt_client.py.extensibility property on both clients
  • pyproject.toml — per-file-ignores for new paths
  • tests/unit/test_mpt_client.pyextensibility added to parametrize lists

Endpoints covered

Method Path How
GET /extensibility/extensions CollectionMixin
POST /extensibility/extensions CreateFileMixin
GET /extensibility/extensions/{id} GetMixin
PUT /extensibility/extensions/{id} UpdateFileMixin
DELETE /extensibility/extensions/{id} DeleteMixin
POST /extensibility/extensions/{id}/publish ExtensionMixin
POST /extensibility/extensions/{id}/unpublish ExtensionMixin
POST /extensibility/extensions/{id}/regenerate ExtensionMixin
POST /extensibility/extensions/{id}/token ExtensionMixin
GET /extensibility/extensions/{id}/icon ExtensionMixin.download_iconFileModel

Sub-resources (documents, instances, media, terms) are out of scope for this ticket.

Quality

  • make check-all passes
  • ✅ 100% unit test coverage on all new/modified code (61 tests)

Closes MPT-19892

  • Add Extension model with typed fields (name, icon, revision, status, website, short/long descriptions, vendor, categories, modules, statistics, configuration, meta, service, audit)
  • Implement ExtensionsService and AsyncExtensionsService for /public/v1/extensibility/extensions with collection, create (file upload support), get, update (file upload support), and delete
  • Add ExtensionMixin and AsyncExtensionMixin with publish, unpublish, regenerate, token, and download_icon (returns FileModel)
  • Introduce Extensibility and AsyncExtensibility accessor classes and expose via mpt_api_client.resources
  • Add .extensibility property to MPTClient and AsyncMPTClient
  • Add package exports and per-file flake8 ignores for new extensibility modules
  • Add comprehensive unit tests (61 tests) covering services, mixins, model construction, icon download, and client access

@albertsola albertsola requested a review from a team as a code owner April 2, 2026 17:58
@albertsola albertsola requested review from d3rky and jentyk April 2, 2026 17:58
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

Adds an extensibility resource package exposing Extensibility and AsyncExtensibility on the clients. Provides an extensions service, Extension model, mixins for extension actions, and accompanying unit tests and lint config updates.

Changes

Cohort / File(s) Summary
Client integration
mpt_api_client/mpt_client.py, tests/unit/test_mpt_client.py
Added .extensibility property to MPTClient and AsyncMPTClient; updated client tests to assert the new resource exposure.
Resource exports & package init
mpt_api_client/resources/__init__.py, mpt_api_client/resources/extensibility/__init__.py, mpt_api_client/resources/extensibility/mixins/__init__.py
Exported Extensibility/AsyncExtensibility and mixins from package-level __init__ modules.
Extensibility resource facades
mpt_api_client/resources/extensibility/extensibility.py
Added Extensibility and AsyncExtensibility classes with .extensions property returning configured services.
Extension model & services
mpt_api_client/resources/extensibility/extensions.py
Added Extension model, ExtensionsServiceConfig, and sync/async ExtensionsService implementations with CRUD, collection mapping, and file-upload configuration.
Extension mixins
mpt_api_client/resources/extensibility/mixins/extension_mixin.py
Added ExtensionMixin and AsyncExtensionMixin methods: publish, unpublish, regenerate, token, and download_icon.
Tests
tests/unit/resources/extensibility/*.py, tests/unit/resources/extensibility/mixins/*.py
New unit tests covering model construction, service methods (create/update/delete/iterate), mixin actions, icon download, and client integration (sync + async).
Linter config
pyproject.toml
Added per-file Flake8 ignore entries for extensibility modules and tests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Jira Issue Key In Title ✅ Passed The PR title contains exactly one Jira issue key in the correct format: MPT-19892
Test Coverage Required ✅ Passed PR includes 4 test files alongside 8 code files, providing comprehensive test coverage (384 lines) for new extensibility features.
Single Commit Required ✅ Passed The PR contains exactly one commit (aae1b3b) with all changes across multiple files included in a single commit, maintaining a clean git history.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…tensions

- Add Extension model with all fields from the OpenAPI spec
- Add ExtensionsService / AsyncExtensionsService with CollectionMixin,
  GetMixin, CreateFileMixin, UpdateFileMixin, DeleteMixin and ExtensionMixin
- Add ExtensionMixin / AsyncExtensionMixin with publish, unpublish,
  regenerate, token and download_icon actions
- Add Extensibility / AsyncExtensibility accessor class
- Wire extensibility property into MPTClient and AsyncMPTClient
- Add full unit test coverage (100%) for all new code

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@albertsola albertsola force-pushed the MPT-19892/extension-base-service branch from 4f36d8b to aae1b3b Compare April 2, 2026 18:01
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/unit/resources/extensibility/test_extensions.py (1)

65-80: Assert multipart keys for file upload contract in create/update tests.

At the moment, Line 77/94/112/130 only validate method and response. These tests won’t catch regressions where multipart keys stop being icon and extension (which are critical config values for CreateFileMixin/UpdateFileMixin).

Suggested assertion pattern
 def test_extension_create(extensions_service, tmp_path):
@@
     assert mock_route.call_count == 1
     assert mock_route.calls[0].request.method == "POST"
+    body = mock_route.calls[0].request.content
+    assert b'name="icon"' in body
+    assert b'name="extension"' in body
     assert result.to_dict() == expected_response
@@
 async def test_async_extension_create(async_extensions_service, tmp_path):
@@
     assert mock_route.call_count == 1
     assert mock_route.calls[0].request.method == "POST"
+    body = mock_route.calls[0].request.content
+    assert b'name="icon"' in body
+    assert b'name="extension"' in body
     assert result.to_dict() == expected_response
@@
 def test_extension_update(extensions_service, tmp_path):
@@
     assert mock_route.call_count == 1
     assert mock_route.calls[0].request.method == "PUT"
+    body = mock_route.calls[0].request.content
+    assert b'name="icon"' in body
+    assert b'name="extension"' in body
     assert result.to_dict() == expected_response
@@
 async def test_async_extension_update(async_extensions_service, tmp_path):
@@
     assert mock_route.call_count == 1
     assert mock_route.calls[0].request.method == "PUT"
+    body = mock_route.calls[0].request.content
+    assert b'name="icon"' in body
+    assert b'name="extension"' in body
     assert result.to_dict() == expected_response

Also applies to: 82-97, 99-115, 117-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/resources/extensibility/test_extensions.py` around lines 65 - 80,
Update the unit tests (e.g., test_extension_create and the related create/update
tests) to assert that the outgoing multipart request uses the expected part
names: the file part is named "icon" and the JSON payload part is named
"extension". In the mocked respx route callbacks (or after the request is made),
inspect mock_route.calls[0].request.content or .read() to parse the multipart
body and assert presence of the "Content-Disposition" names "name=\"icon\"" and
"name=\"extension\"" so the CreateFileMixin/UpdateFileMixin contract is
enforced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/resources/extensibility/test_extensions.py`:
- Around line 65-80: Update the unit tests (e.g., test_extension_create and the
related create/update tests) to assert that the outgoing multipart request uses
the expected part names: the file part is named "icon" and the JSON payload part
is named "extension". In the mocked respx route callbacks (or after the request
is made), inspect mock_route.calls[0].request.content or .read() to parse the
multipart body and assert presence of the "Content-Disposition" names
"name=\"icon\"" and "name=\"extension\"" so the CreateFileMixin/UpdateFileMixin
contract is enforced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 56d6da9e-0377-42f6-bb54-604ebe64c688

📥 Commits

Reviewing files that changed from the base of the PR and between 08be40f and 4f36d8b.

📒 Files selected for processing (14)
  • mpt_api_client/mpt_client.py
  • mpt_api_client/resources/__init__.py
  • mpt_api_client/resources/extensibility/__init__.py
  • mpt_api_client/resources/extensibility/extensibility.py
  • mpt_api_client/resources/extensibility/extensions.py
  • mpt_api_client/resources/extensibility/mixins/__init__.py
  • mpt_api_client/resources/extensibility/mixins/extension_mixin.py
  • pyproject.toml
  • tests/unit/resources/extensibility/__init__.py
  • tests/unit/resources/extensibility/mixins/__init__.py
  • tests/unit/resources/extensibility/mixins/test_extension_mixin.py
  • tests/unit/resources/extensibility/test_extensibility.py
  • tests/unit/resources/extensibility/test_extensions.py
  • tests/unit/test_mpt_client.py

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 2, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/unit/resources/extensibility/test_extensions.py (2)

23-58: Deduplicate the mixin method matrix to avoid drift.

Line 25-36 and Line 47-57 repeat the same list; centralizing it will reduce maintenance risk.

Refactor suggestion
+MIXIN_METHODS = (
+    "get",
+    "create",
+    "update",
+    "delete",
+    "publish",
+    "unpublish",
+    "regenerate",
+    "token",
+    "download_icon",
+    "iterate",
+)
+
 `@pytest.mark.parametrize`(
     "method",
-    [
-        "get",
-        "create",
-        "update",
-        "delete",
-        "publish",
-        "unpublish",
-        "regenerate",
-        "token",
-        "download_icon",
-        "iterate",
-    ],
+    MIXIN_METHODS,
 )
 def test_mixins_present(extensions_service, method):
@@
 `@pytest.mark.parametrize`(
     "method",
-    [
-        "get",
-        "create",
-        "update",
-        "delete",
-        "publish",
-        "unpublish",
-        "regenerate",
-        "token",
-        "download_icon",
-        "iterate",
-    ],
+    MIXIN_METHODS,
 )
 def test_async_mixins_present(async_extensions_service, method):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/resources/extensibility/test_extensions.py` around lines 23 - 58,
The test repeats the same param list twice; extract the list of mixin names into
a single constant and reuse it in both pytest.mark.parametrize decorators to
avoid duplication—e.g., define a module-level tuple/variable (e.g.,
MIXIN_METHODS or METHODS) containing
"get","create","update","delete","publish","unpublish","regenerate","token","download_icon","iterate"
and update the two pytest.mark.parametrize usages around test_mixins_present and
the following test to reference that variable instead of repeating the literal
list.

65-133: Add multipart payload assertions for upload key contract.

The create/update tests assert method and count, but they don’t verify multipart field names (icon file part and extension JSON part). A direct assertion here would better protect this endpoint contract.

Assertion enhancement example
     assert mock_route.call_count == 1
     assert mock_route.calls[0].request.method == "POST"
+    request_body = mock_route.calls[0].request.content
+    assert b'name="icon"' in request_body
+    assert b'name="extension"' in request_body
     assert result.to_dict() == expected_response
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/resources/extensibility/test_extensions.py` around lines 65 - 133,
Tests for test_extension_create, test_async_extension_create,
test_extension_update, and test_async_extension_update currently only assert
call count and method; add assertions that the outgoing requests are
multipart/form-data and include the expected field names: verify
mock_route.calls[0].request.headers["content-type"] starts with
"multipart/form-data" and parse the request body to assert there is an "icon"
file part (with a filename) and an "extension" part containing the JSON for
extension_data/update_data; update both synchronous
(extensions_service.create/update) and asynchronous
(async_extensions_service.create/update) tests to include these multipart field
name and content assertions so the upload key contract is enforced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/resources/extensibility/test_extensions.py`:
- Around line 23-58: The test repeats the same param list twice; extract the
list of mixin names into a single constant and reuse it in both
pytest.mark.parametrize decorators to avoid duplication—e.g., define a
module-level tuple/variable (e.g., MIXIN_METHODS or METHODS) containing
"get","create","update","delete","publish","unpublish","regenerate","token","download_icon","iterate"
and update the two pytest.mark.parametrize usages around test_mixins_present and
the following test to reference that variable instead of repeating the literal
list.
- Around line 65-133: Tests for test_extension_create,
test_async_extension_create, test_extension_update, and
test_async_extension_update currently only assert call count and method; add
assertions that the outgoing requests are multipart/form-data and include the
expected field names: verify mock_route.calls[0].request.headers["content-type"]
starts with "multipart/form-data" and parse the request body to assert there is
an "icon" file part (with a filename) and an "extension" part containing the
JSON for extension_data/update_data; update both synchronous
(extensions_service.create/update) and asynchronous
(async_extensions_service.create/update) tests to include these multipart field
name and content assertions so the upload key contract is enforced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 36d07b26-4128-47fb-a998-0d1322db1d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 4f36d8b and aae1b3b.

📒 Files selected for processing (14)
  • mpt_api_client/mpt_client.py
  • mpt_api_client/resources/__init__.py
  • mpt_api_client/resources/extensibility/__init__.py
  • mpt_api_client/resources/extensibility/extensibility.py
  • mpt_api_client/resources/extensibility/extensions.py
  • mpt_api_client/resources/extensibility/mixins/__init__.py
  • mpt_api_client/resources/extensibility/mixins/extension_mixin.py
  • pyproject.toml
  • tests/unit/resources/extensibility/__init__.py
  • tests/unit/resources/extensibility/mixins/__init__.py
  • tests/unit/resources/extensibility/mixins/test_extension_mixin.py
  • tests/unit/resources/extensibility/test_extensibility.py
  • tests/unit/resources/extensibility/test_extensions.py
  • tests/unit/test_mpt_client.py
✅ Files skipped from review due to trivial changes (5)
  • mpt_api_client/resources/extensibility/extensibility.py
  • pyproject.toml
  • tests/unit/test_mpt_client.py
  • tests/unit/resources/extensibility/test_extensibility.py
  • tests/unit/resources/extensibility/mixins/test_extension_mixin.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • mpt_api_client/resources/init.py
  • mpt_api_client/mpt_client.py
  • mpt_api_client/resources/extensibility/mixins/extension_mixin.py

@albertsola albertsola merged commit 3aecc19 into main Apr 2, 2026
4 checks passed
@albertsola albertsola deleted the MPT-19892/extension-base-service branch April 2, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants