-
Notifications
You must be signed in to change notification settings - Fork 469
feat: synchronize-openapi-schema-with-gram #6499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 3 Skipped Deployments
|
for more information, see https://pre-commit.ci
Docker builds report
|
…m:Flagsmith/flagsmith into feat/synchronize-openapi-schema-with-gram
|
Adding a push to gram CI step - testing it with |
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6499 +/- ##
========================================
Coverage 98.09% 98.10%
========================================
Files 1293 1295 +2
Lines 46607 46811 +204
========================================
+ Hits 45719 45923 +204
Misses 888 888 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
emyller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few questions, and requests. This is good work so far.
| make install-packages opts="--with saml,auth-controller,workflows,release-pipelines" | ||
| make install-private-modules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this could cause unwanted exposure of our private modules somehow. Can you please confirm what is extracted from private modules and exposed in the MCP server?
| GRAM_ORG: ${{ secrets.GRAM_ORG }} | ||
| GRAM_PROJECT: ${{ secrets.GRAM_PROJECT }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these really sensitive?
| if self.MCP_TAG in tags: | ||
| filtered_operations[method] = self._transform_for_mcp(operation) | ||
|
|
||
| if any(isinstance(op, dict) for op in filtered_operations.values()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if any(isinstance(op, dict) for op in filtered_operations.values()): | |
| if has_any_mcp_tag: |
I think this would improve with a bool properly managed in the logic above.
| if ( | ||
| getattr(self, "request", None) | ||
| and self.request.query_params.get("mcp", "").lower() == "true" | ||
| ): | ||
| return MCPSchemaGenerator | ||
| return SchemaGenerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if ( | |
| getattr(self, "request", None) | |
| and self.request.query_params.get("mcp", "").lower() == "true" | |
| ): | |
| return MCPSchemaGenerator | |
| return SchemaGenerator | |
| try: | |
| if self.request.query_params["mcp"].lower() == "true": | |
| return MCPSchemaGenerator | |
| except (AttributeError, KeyError,): | |
| pass | |
| return SchemaGenerator |
Python suggests EAFP over LBYL. I personally find it easier to read in EAFP.
| def get_generator_class(self) -> type: | ||
| if ( | ||
| getattr(self, "request", None) | ||
| and self.request.query_params.get("mcp", "").lower() == "true" | ||
| ): | ||
| return MCPSchemaGenerator | ||
| return SchemaGenerator | ||
|
|
||
| def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: | ||
| self.generator_class = self.get_generator_class() | ||
| return super().get(request, *args, **kwargs) # type: ignore[no-untyped-call, no-any-return] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please remove code duplication and move these methods to a base private class?
| def test_custom_json_view__returns_mcp_generator_when_mcp_param_is_true() -> None: | ||
| # Given | ||
| view = CustomSpectacularJSONAPIView() | ||
| view.request = MagicMock() | ||
| view.request.query_params = {"mcp": "true"} | ||
|
|
||
| # When | ||
| generator_class = view.get_generator_class() | ||
|
|
||
| # Then | ||
| assert generator_class is MCPSchemaGenerator | ||
|
|
||
|
|
||
| def test_custom_json_view__returns_schema_generator_when_mcp_param_is_false() -> None: | ||
| # Given | ||
| view = CustomSpectacularJSONAPIView() | ||
| view.request = MagicMock() | ||
| view.request.query_params = {"mcp": "false"} | ||
|
|
||
| # When | ||
| generator_class = view.get_generator_class() | ||
|
|
||
| # Then | ||
| assert generator_class is SchemaGenerator | ||
|
|
||
|
|
||
| def test_custom_json_view__returns_schema_generator_when_no_mcp_param() -> None: | ||
| # Given | ||
| view = CustomSpectacularJSONAPIView() | ||
| view.request = MagicMock() | ||
| view.request.query_params = {} | ||
|
|
||
| # When | ||
| generator_class = view.get_generator_class() | ||
|
|
||
| # Then | ||
| assert generator_class is SchemaGenerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can improve with @pytest.mark.parametrize.
| def test_custom_yaml_view__returns_mcp_generator_when_mcp_param_is_true() -> None: | ||
| # Given | ||
| view = CustomSpectacularYAMLAPIView() | ||
| view.request = MagicMock() | ||
| view.request.query_params = {"mcp": "true"} | ||
|
|
||
| # When | ||
| generator_class = view.get_generator_class() | ||
|
|
||
| # Then | ||
| assert generator_class is MCPSchemaGenerator | ||
|
|
||
|
|
||
| def test_custom_yaml_view__returns_schema_generator_when_no_mcp_param() -> None: | ||
| # Given | ||
| view = CustomSpectacularYAMLAPIView() | ||
| view.request = MagicMock() | ||
| view.request.query_params = {} | ||
|
|
||
| # When | ||
| generator_class = view.get_generator_class() | ||
|
|
||
| # Then | ||
| assert generator_class is SchemaGenerator | ||
|
|
||
|
|
||
| def test_custom_json_view__case_insensitive_mcp_param() -> None: | ||
| # Given | ||
| view = CustomSpectacularJSONAPIView() | ||
| view.request = MagicMock() | ||
| view.request.query_params = {"mcp": "TRUE"} | ||
|
|
||
| # When | ||
| generator_class = view.get_generator_class() | ||
|
|
||
| # Then | ||
| assert generator_class is MCPSchemaGenerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can improve with @pytest.mark.parametrize.
| def test_mcp_schema__includes_organisations_endpoint() -> None: | ||
| # Given | ||
| generator = MCPSchemaGenerator() | ||
|
|
||
| # When | ||
| schema = generator.get_schema(request=None, public=True) | ||
|
|
||
| # Then | ||
| assert "/api/v1/organisations/" in schema["paths"] | ||
| org_list = schema["paths"]["/api/v1/organisations/"]["get"] | ||
| assert org_list["x-gram"] == { | ||
| "name": "list_organizations", | ||
| "description": "Lists all organizations accessible with the provided user API key.", | ||
| } | ||
|
|
||
|
|
||
| def test_mcp_schema__includes_organisation_projects_endpoint() -> None: | ||
| # Given | ||
| generator = MCPSchemaGenerator() | ||
|
|
||
| # When | ||
| schema = generator.get_schema(request=None, public=True) | ||
|
|
||
| # Then | ||
| assert "/api/v1/organisations/{id}/projects/" in schema["paths"] | ||
| projects_list = schema["paths"]["/api/v1/organisations/{id}/projects/"]["get"] | ||
| assert projects_list["x-gram"] == { | ||
| "name": "list_projects_in_organization", | ||
| "description": "Retrieves all projects within a specified organization.", | ||
| } | ||
|
|
||
|
|
||
| def test_mcp_schema__excludes_non_mcp_endpoints() -> None: | ||
| # Given | ||
| generator = MCPSchemaGenerator() | ||
|
|
||
| # When | ||
| schema = generator.get_schema(request=None, public=True) | ||
|
|
||
| # Then | ||
| # Users endpoint should not be in MCP schema (not tagged) | ||
| assert "/api/v1/users/" not in schema["paths"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this could be one test having assert schema["paths"] == ....
| @@ -0,0 +1,12 @@ | |||
| { | |||
| "schema_version": "1.0.0", | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just making sure we're actually tagging 1.0.0 in this product, or perhaps start with 0.0.x?
| git clone https://github.com/flagsmith/flagsmith-saml --depth 1 --branch ${SAML_REVISION} && rm -rf $(SITE_PACKAGES_DIR)/saml && mv ./flagsmith-saml/saml $(SITE_PACKAGES_DIR) | ||
| git clone https://github.com/flagsmith/flagsmith-rbac --depth 1 --branch ${RBAC_REVISION} && rm -rf $(SITE_PACKAGES_DIR)/rbac && mv ./flagsmith-rbac/rbac $(SITE_PACKAGES_DIR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain the use case? Maybe this helped you locally?
Thanks for submitting a PR! Please check the boxes below:
docs/if required so people know about the feature.Changes
Contributes to #6488
/api/v1/mcp-schema/endpoint serving the filtered OpenAPI specgenerate-mcp-specto generate it locallyGRAM_PROJECT,GRAM_ORG,GRAM_API_KEYTag a new endpoint
How did you test this code?