imbi-api 2.26.0__tar.gz → 2.26.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {imbi_api-2.26.0 → imbi_api-2.26.1}/PKG-INFO +2 -2
- {imbi_api-2.26.0 → imbi_api-2.26.1}/pyproject.toml +2 -2
- imbi_api-2.26.1/src/imbi/api/component_facts.py +348 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/components.py +162 -226
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_deployments.py +1 -1
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/projects.py +3 -1
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/releases.py +1 -1
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/search.py +14 -10
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/sbom.py +46 -65
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_components.py +380 -105
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_releases.py +89 -33
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_search.py +30 -6
- imbi_api-2.26.1/tests/test_component_facts.py +161 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/.gitignore +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/README.md +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/moon.yml +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/README.md +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/app.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/authorization_codes.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/internal_services.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/local_auth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/login_providers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/membership.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/oauth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/oauth_clients.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/password.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/permissions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/principals.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/seed.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/sessions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/tokens.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/auth/totp.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/backfill_embeddings.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/blueprint_attributes.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/blueprint_compliance.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/commit_sync/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/commit_sync/queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/commit_sync/service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/deployment_migration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/deployment_sweeper.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/deployment_sync/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/deployment_sync/queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/deployment_sync/service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/documents/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/documents/read_sweeper.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/domain/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/domain/models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/domain/scoring.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/drift.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/client.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/dependencies.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates/base.html +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates/base.txt +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates/password_reset.html +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates/password_reset.txt +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates/welcome.html +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates/welcome.txt +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/email/templates.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_credentials.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_document_events.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_document_history.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_document_reads.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_document_scope.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_helpers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_json_fields.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_pagination.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_request_urls.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/_search_index.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/admin.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/admin_plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/api_keys.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/auth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/auth_providers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/blueprints.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/client_credentials.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/comments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/dashboard.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/document_analytics.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/document_likes.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/document_presence.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/document_templates.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/document_versions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/documents.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/environments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/events.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/graph_query.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/integrations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/link_definitions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/local_auth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/maintenance.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/mcp_servers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/mfa.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/operations_log.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/organizations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/plugin_edges.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/plugin_entities.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_analysis.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_configuration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_incidents.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_integrations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_logs.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/project_types.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/pull_requests.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/roles.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/scoring.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/scoring_policies.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/service_accounts.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/status.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/tags.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/teams.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/uploads.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/user_activity.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/users.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/endpoints/webhooks.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/entrypoint.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/graph_sql.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/attribution.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/endpoints.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/errors.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/flows.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/host_integration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/repository.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/resolution.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/state.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/identity/sweeper.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/lifespans.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/llm/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/llm/dependencies.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/maintenance/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/maintenance/log.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/maintenance/operations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/maintenance/registry.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/maintenance/state.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/maintenance/worker.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/mcp_test.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/middleware/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/middleware/rate_limit.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/openapi.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/assignment_writer.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/assignments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/credentials.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/installer.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/lifecycle.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/lifecycle_dispatch.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/reload.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/resolution.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/plugins/schemas.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/pr_sync/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/pr_sync/queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/pr_sync/service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/prompts/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/prompts/release_notes_system.md +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/py.typed +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/relationships.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/release_promote/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/release_promote/queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/release_promote/service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/sbom_backfill.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/scoring/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/scoring/queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/settings.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/storage/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/storage/client.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/storage/dependencies.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/storage/thumbnails.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/src/imbi/api/storage/validation.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_authentication.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_authorization.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_encryption.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_internal_services.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_login_providers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_membership.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_oauth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_oauth_server.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_permissions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_seed.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_sessions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/auth/test_totp.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/clickhouse/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/clickhouse/test_client.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/clickhouse/test_init.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/clickhouse/test_privacy.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/email/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/email/test_client.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/email/test_init.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/email/test_integration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/email/test_models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/email/test_templates.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_admin.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_admin_plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_api_keys.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_auth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_auth_providers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_blueprints.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_client_credentials.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_comments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_credentials.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_dashboard.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_analytics.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_likes.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_presence.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_reads.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_reads_sql.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_templates.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_document_versions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_documents.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_environments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_events.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_graph_query.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_init.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_integrations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_json_fields.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_link_definitions.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_local_auth.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_maintenance.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_mcp_servers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_mfa.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_operations_log.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_organizations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_pagination.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_plugin_label_validation.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_analysis.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_commit_sync.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_configuration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_deployments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_incidents.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_integrations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_lifecycle.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_logs.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_response_surfacing.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_project_types.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_projects.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_projects_helpers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_pull_requests.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_request_urls.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_roles.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_sa_api_keys.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_scoring.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_scoring_policies.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_search_index.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_service_accounts.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_service_writeback.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_status.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_tags.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_teams.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_uploads.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_user_activity.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_users.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/endpoints/test_webhooks.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/fixtures/sbom/npm-realistic.json +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/fixtures/sbom/pypi-realistic.json +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/fixtures/sbom/tiny.json +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_endpoints.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_errors.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_flows.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_host_integration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_repository.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_resolution.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_state.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/identity/test_sweeper.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/middleware/test_rate_limit.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/storage/__init__.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/storage/test_client.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/storage/test_init.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/storage/test_thumbnails.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/storage/test_validation.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/support.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_app.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_assignment_writer.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_backfill_embeddings.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_blueprint_attributes.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_blueprint_compliance.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_blueprints.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_commit_sync_queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_commit_sync_service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_deployment_migration.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_deployment_sweeper.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_deployment_sync_queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_deployment_sync_service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_drift.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_entrypoint.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_graph_sql.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_identity_attribution.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_init.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_installer.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_lifecycle_dispatch.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_lifespans.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_maintenance_log.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_maintenance_operations.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_maintenance_state.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_maintenance_worker.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_models.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_openapi.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_plugin_schemas.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_plugins.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_plugins_assignments.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_plugins_credentials.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_pr_sync_queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_pr_sync_service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_release_promote_service.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_sbom.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_sbom_backfill.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_scoring_queue.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_scoring_triggers.py +0 -0
- {imbi_api-2.26.0 → imbi_api-2.26.1}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: imbi-api
|
|
3
|
-
Version: 2.26.
|
|
3
|
+
Version: 2.26.1
|
|
4
4
|
Summary: Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications.
|
|
5
5
|
Author-email: Alex Campbell <alexc@aweber.com>, Dave Shawley <daves@aweber.com>, "Gavin M. Roy" <gavinr@aweber.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -19,7 +19,7 @@ Requires-Dist: cyclonedx-python-lib<12,>=11.7
|
|
|
19
19
|
Requires-Dist: fastapi
|
|
20
20
|
Requires-Dist: filetype
|
|
21
21
|
Requires-Dist: httpx<1,>=0.28.1
|
|
22
|
-
Requires-Dist: imbi-common[databases,llm,sentry,server]==2.26.
|
|
22
|
+
Requires-Dist: imbi-common[databases,llm,sentry,server]==2.26.1
|
|
23
23
|
Requires-Dist: jinja2
|
|
24
24
|
Requires-Dist: jsonpatch>=1.33
|
|
25
25
|
Requires-Dist: mcp>=1.26.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "imbi-api"
|
|
3
|
-
version = "2.26.
|
|
3
|
+
version = "2.26.1"
|
|
4
4
|
description = "Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -28,7 +28,7 @@ dependencies = [
|
|
|
28
28
|
"fastapi",
|
|
29
29
|
"filetype",
|
|
30
30
|
"httpx>=0.28.1,<1",
|
|
31
|
-
"imbi-common[databases,llm,sentry,server]==2.26.
|
|
31
|
+
"imbi-common[databases,llm,sentry,server]==2.26.1",
|
|
32
32
|
"jinja2",
|
|
33
33
|
"jsonpatch>=1.33",
|
|
34
34
|
"mcp>=1.26.0",
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""Read SBoM component usage facts from ClickHouse.
|
|
2
|
+
|
|
3
|
+
Phase three of the SBoM component migration. Every read that used to
|
|
4
|
+
answer "which components does this release use" by traversing
|
|
5
|
+
``USES_COMPONENT_RELEASE`` asks this module instead. The edge's fan-in
|
|
6
|
+
is what made those reads unusable -- counting the label outright fails
|
|
7
|
+
with ``could not resize shared memory segment`` -- and no amount of
|
|
8
|
+
Cypher rewriting fixes an unbounded incoming traversal.
|
|
9
|
+
|
|
10
|
+
What stays in the graph is identity and governance: the ``Component``
|
|
11
|
+
and ``ComponentRelease`` nodes, their statuses, advisories, notes, and
|
|
12
|
+
the project/team/environment topology. What moves here is the usage
|
|
13
|
+
fact, which is a per-release immutable snapshot and was never a good
|
|
14
|
+
fit for a graph edge.
|
|
15
|
+
|
|
16
|
+
Two graph primitives feed these reads and they are not interchangeable:
|
|
17
|
+
|
|
18
|
+
- The **org project set** -- non-archived project ids of an
|
|
19
|
+
organization. Hundreds of ids, no fan-in. It scopes authorization,
|
|
20
|
+
search, and the catalog totals, and it deliberately spans every
|
|
21
|
+
release a project has ever had.
|
|
22
|
+
- The **deployment pointer set** -- the releases that
|
|
23
|
+
``(:Project)-[d:DEPLOYED_IN]->(:Environment)`` currently points at
|
|
24
|
+
via ``d.current_release``. Around a thousand ids. It scopes the
|
|
25
|
+
reports, which describe what is running now.
|
|
26
|
+
|
|
27
|
+
Substituting one for the other silently changes what a screen means, so
|
|
28
|
+
every function here takes the ids it needs rather than deriving them.
|
|
29
|
+
|
|
30
|
+
Every read resolves each release to exactly one batch before reading
|
|
31
|
+
its rows, using ``argMax(batch_id, (source = 'ingest', recorded_at))``.
|
|
32
|
+
``source`` leads the tuple deliberately: an ingest batch outranks a
|
|
33
|
+
backfill one whatever order the two land in, so a backfill running
|
|
34
|
+
beside live traffic can never displace a real SBoM. Reading
|
|
35
|
+
``release_components`` without that join reads every snapshot a release
|
|
36
|
+
has ever had, superseded ones included.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import typing
|
|
42
|
+
|
|
43
|
+
from imbi.common import clickhouse, graph
|
|
44
|
+
|
|
45
|
+
#: Non-archived project ids of an organization -- the org project set.
|
|
46
|
+
#: Followed outward from the organization, so it is bounded by the
|
|
47
|
+
#: number of projects rather than by the component catalog.
|
|
48
|
+
ORG_PROJECT_IDS: typing.LiteralString = """
|
|
49
|
+
MATCH (:Organization {{slug: {org_slug}}})<-[:BELONGS_TO]-(:Team)
|
|
50
|
+
<-[:OWNED_BY]-(p:Project)
|
|
51
|
+
WHERE coalesce(p.archived, false) = false
|
|
52
|
+
RETURN p.id AS project_id
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
#: The deployment pointer set, with the topology each report row needs.
|
|
56
|
+
#: ``d.current_release`` is a property lookup, not an event replay, so
|
|
57
|
+
#: this stays cheap however long a project's deployment history is.
|
|
58
|
+
ORG_DEPLOYED_RELEASES: typing.LiteralString = """
|
|
59
|
+
MATCH (p:Project)-[d:DEPLOYED_IN]->(e:Environment)
|
|
60
|
+
MATCH (p)-[:OWNED_BY]->(t:Team)-[:BELONGS_TO]->
|
|
61
|
+
(:Organization {{slug: {org_slug}}})
|
|
62
|
+
WHERE coalesce(p.archived, false) = false
|
|
63
|
+
OPTIONAL MATCH (p)-[:TYPE]->(pt:ProjectType)
|
|
64
|
+
RETURN d.current_release AS release_id,
|
|
65
|
+
p.id AS project_id,
|
|
66
|
+
p.name AS project_name,
|
|
67
|
+
p.slug AS project_slug,
|
|
68
|
+
t.name AS team_name,
|
|
69
|
+
t.slug AS team_slug,
|
|
70
|
+
e.name AS environment_name,
|
|
71
|
+
e.slug AS environment_slug,
|
|
72
|
+
e.label_color AS environment_color,
|
|
73
|
+
collect(DISTINCT pt.name) AS project_types
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
#: Resolve each release of a set of projects to its winning batch.
|
|
77
|
+
_BATCHES_BY_PROJECT = (
|
|
78
|
+
'SELECT release_id,'
|
|
79
|
+
" argMax(batch_id, (source = 'ingest', recorded_at)) AS batch_id"
|
|
80
|
+
' FROM imbi.release_component_batches'
|
|
81
|
+
' WHERE project_id IN {project_ids:Array(String)}'
|
|
82
|
+
' GROUP BY release_id'
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
#: The same resolution anchored on releases. Used by the reports, whose
|
|
86
|
+
#: scope is the deployment pointer set rather than whole projects.
|
|
87
|
+
_BATCHES_BY_RELEASE = (
|
|
88
|
+
'SELECT release_id,'
|
|
89
|
+
" argMax(batch_id, (source = 'ingest', recorded_at)) AS batch_id"
|
|
90
|
+
' FROM imbi.release_component_batches'
|
|
91
|
+
' WHERE release_id IN {release_ids:Array(String)}'
|
|
92
|
+
' GROUP BY release_id'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
_JOIN = (
|
|
96
|
+
' INNER JOIN ({batches}) AS b'
|
|
97
|
+
' ON c.release_id = b.release_id AND c.batch_id = b.batch_id'
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _by_project(select: str, where: str = '') -> str:
|
|
102
|
+
"""Compose a project-scoped read against the winning batches."""
|
|
103
|
+
return (
|
|
104
|
+
f'{select} FROM imbi.release_components AS c'
|
|
105
|
+
+ _JOIN.format(batches=_BATCHES_BY_PROJECT)
|
|
106
|
+
+ ' WHERE c.project_id IN {project_ids:Array(String)}'
|
|
107
|
+
+ where
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _by_release(select: str, where: str = '') -> str:
|
|
112
|
+
"""Compose a release-scoped read against the winning batches."""
|
|
113
|
+
return (
|
|
114
|
+
f'{select} FROM imbi.release_components AS c'
|
|
115
|
+
+ _JOIN.format(batches=_BATCHES_BY_RELEASE)
|
|
116
|
+
+ ' WHERE c.release_id IN {release_ids:Array(String)}'
|
|
117
|
+
+ where
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def org_project_ids(db: graph.Graph, org_slug: str) -> list[str]:
|
|
122
|
+
"""Return the org project set, or an empty list for an unknown org."""
|
|
123
|
+
rows = await db.execute(
|
|
124
|
+
ORG_PROJECT_IDS, {'org_slug': org_slug}, ['project_id']
|
|
125
|
+
)
|
|
126
|
+
# AGE hands back JSON-encoded scalars; ``str()`` on one of those
|
|
127
|
+
# yields a quoted id that matches nothing in ClickHouse.
|
|
128
|
+
decoded = (graph.parse_agtype(row.get('project_id')) for row in rows)
|
|
129
|
+
return [str(value) for value in decoded if value]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def component_ids_in_org(
|
|
133
|
+
project_ids: list[str],
|
|
134
|
+
component_ids: list[str] | None = None,
|
|
135
|
+
) -> set[str]:
|
|
136
|
+
"""Which component ids the org's projects currently depend on.
|
|
137
|
+
|
|
138
|
+
Feeds the global search org scope, which needs the whole set, and
|
|
139
|
+
package search, which needs only the subset its name match already
|
|
140
|
+
produced. Passing *component_ids* narrows the aggregate to those,
|
|
141
|
+
so a keystroke intersects against its own candidates rather than
|
|
142
|
+
materialising the org's entire catalog to discard most of it.
|
|
143
|
+
|
|
144
|
+
Unlike the traversal this replaces, the cost either way is one
|
|
145
|
+
aggregate over the org's own rows rather than a fan-in from every
|
|
146
|
+
``ComponentRelease`` in the graph.
|
|
147
|
+
"""
|
|
148
|
+
if not project_ids or component_ids == []:
|
|
149
|
+
return set()
|
|
150
|
+
where = ''
|
|
151
|
+
params: dict[str, typing.Any] = {'project_ids': project_ids}
|
|
152
|
+
if component_ids is not None:
|
|
153
|
+
where = ' AND c.component_id IN {component_ids:Array(String)}'
|
|
154
|
+
params['component_ids'] = component_ids
|
|
155
|
+
rows = await clickhouse.query(
|
|
156
|
+
_by_project('SELECT DISTINCT c.component_id AS component_id', where),
|
|
157
|
+
params,
|
|
158
|
+
)
|
|
159
|
+
return {str(row['component_id']) for row in rows}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def component_in_org(project_ids: list[str], component_id: str) -> bool:
|
|
163
|
+
"""Whether any of ``project_ids`` depends on ``component_id``.
|
|
164
|
+
|
|
165
|
+
``component_id`` leads ``release_components``' sort key, so this is
|
|
166
|
+
a prefix seek rather than a scan however large the table gets.
|
|
167
|
+
"""
|
|
168
|
+
if not project_ids:
|
|
169
|
+
return False
|
|
170
|
+
rows = await clickhouse.query(
|
|
171
|
+
_by_project(
|
|
172
|
+
'SELECT 1 AS hit',
|
|
173
|
+
' AND c.component_id = {component_id:String} LIMIT 1',
|
|
174
|
+
),
|
|
175
|
+
{'project_ids': project_ids, 'component_id': component_id},
|
|
176
|
+
)
|
|
177
|
+
return bool(rows)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def component_release_in_org(
|
|
181
|
+
project_ids: list[str],
|
|
182
|
+
component_id: str,
|
|
183
|
+
component_release_id: str,
|
|
184
|
+
) -> bool:
|
|
185
|
+
"""Whether any of ``project_ids`` depends on that specific version.
|
|
186
|
+
|
|
187
|
+
Takes the owning ``component_id`` as well as the version's own id
|
|
188
|
+
purely to keep the sort-key prefix seek: ``component_release_id``
|
|
189
|
+
is the fourth key column, so filtering on it alone scans. The
|
|
190
|
+
caller already has the parent from the graph, where one
|
|
191
|
+
``HAS_RELEASE`` hop resolves it.
|
|
192
|
+
"""
|
|
193
|
+
if not project_ids:
|
|
194
|
+
return False
|
|
195
|
+
rows = await clickhouse.query(
|
|
196
|
+
_by_project(
|
|
197
|
+
'SELECT 1 AS hit',
|
|
198
|
+
' AND c.component_id = {component_id:String}'
|
|
199
|
+
' AND c.component_release_id = {component_release_id:String}'
|
|
200
|
+
' LIMIT 1',
|
|
201
|
+
),
|
|
202
|
+
{
|
|
203
|
+
'project_ids': project_ids,
|
|
204
|
+
'component_id': component_id,
|
|
205
|
+
'component_release_id': component_release_id,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
return bool(rows)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def search_counts(
|
|
212
|
+
project_ids: list[str], component_ids: list[str]
|
|
213
|
+
) -> dict[str, tuple[int, int]]:
|
|
214
|
+
"""Return ``(version_count, project_count)`` per component id.
|
|
215
|
+
|
|
216
|
+
``uniqExact`` rather than ``uniq``: these are small cardinalities
|
|
217
|
+
shown as literal numbers beside a package name, so an approximate
|
|
218
|
+
count would be visibly wrong for no useful saving.
|
|
219
|
+
"""
|
|
220
|
+
if not project_ids or not component_ids:
|
|
221
|
+
return {}
|
|
222
|
+
rows = await clickhouse.query(
|
|
223
|
+
_by_project(
|
|
224
|
+
'SELECT c.component_id AS component_id,'
|
|
225
|
+
' uniqExact(c.component_release_id) AS version_count,'
|
|
226
|
+
' uniqExact(c.project_id) AS project_count',
|
|
227
|
+
' AND c.component_id IN {component_ids:Array(String)}'
|
|
228
|
+
' GROUP BY c.component_id',
|
|
229
|
+
),
|
|
230
|
+
{'project_ids': project_ids, 'component_ids': component_ids},
|
|
231
|
+
)
|
|
232
|
+
return {
|
|
233
|
+
str(row['component_id']): (
|
|
234
|
+
int(row['version_count']),
|
|
235
|
+
int(row['project_count']),
|
|
236
|
+
)
|
|
237
|
+
for row in rows
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
async def ecosystem_totals(project_ids: list[str]) -> dict[str, int]:
|
|
242
|
+
"""Component counts per ecosystem, scoped to the organization.
|
|
243
|
+
|
|
244
|
+
Restores the scoping traded away for speed when this was a graph
|
|
245
|
+
traversal: it counted every component in the catalog because
|
|
246
|
+
traversing to the org cost 36.5s against 1.07s. The aggregate here
|
|
247
|
+
is org-scoped and still cheap, so the chip labels describe what the
|
|
248
|
+
reader's organization actually depends on again.
|
|
249
|
+
"""
|
|
250
|
+
if not project_ids:
|
|
251
|
+
return {}
|
|
252
|
+
rows = await clickhouse.query(
|
|
253
|
+
_by_project(
|
|
254
|
+
'SELECT c.ecosystem AS ecosystem,'
|
|
255
|
+
' uniqExact(c.component_id) AS total',
|
|
256
|
+
" AND c.ecosystem != '' GROUP BY c.ecosystem",
|
|
257
|
+
),
|
|
258
|
+
{'project_ids': project_ids},
|
|
259
|
+
)
|
|
260
|
+
return {str(row['ecosystem']): int(row['total']) for row in rows}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
async def component_usage(
|
|
264
|
+
release_ids: list[str], component_id: str
|
|
265
|
+
) -> list[tuple[str, str, str]]:
|
|
266
|
+
"""``(release_id, component_release_id, version)`` for one package.
|
|
267
|
+
|
|
268
|
+
Scoped to the deployment pointer set, so the result describes what
|
|
269
|
+
is running now rather than every release that ever named the
|
|
270
|
+
package. The caller joins each ``release_id`` back to the project,
|
|
271
|
+
team, and environment it came from -- that topology stayed in the
|
|
272
|
+
graph, and it is what produced the ids passed in here.
|
|
273
|
+
"""
|
|
274
|
+
if not release_ids:
|
|
275
|
+
return []
|
|
276
|
+
rows = await clickhouse.query(
|
|
277
|
+
_by_release(
|
|
278
|
+
'SELECT DISTINCT c.release_id AS release_id,'
|
|
279
|
+
' c.component_release_id AS component_release_id,'
|
|
280
|
+
' c.version AS version',
|
|
281
|
+
' AND c.component_id = {component_id:String}',
|
|
282
|
+
),
|
|
283
|
+
{'release_ids': release_ids, 'component_id': component_id},
|
|
284
|
+
)
|
|
285
|
+
return [
|
|
286
|
+
(
|
|
287
|
+
str(row['release_id']),
|
|
288
|
+
str(row['component_release_id']),
|
|
289
|
+
str(row['version']),
|
|
290
|
+
)
|
|
291
|
+
for row in rows
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
async def governed_usage(
|
|
296
|
+
release_ids: list[str], component_release_ids: list[str]
|
|
297
|
+
) -> list[dict[str, str]]:
|
|
298
|
+
"""Deployed usages of a governed set of component versions.
|
|
299
|
+
|
|
300
|
+
Both bounds come from the graph and both are small: the deployment
|
|
301
|
+
pointer set on one side, and on the other the versions carrying a
|
|
302
|
+
status or an advisory -- the anchor that kept Problem Packages
|
|
303
|
+
starting from the governed set rather than from the catalog.
|
|
304
|
+
"""
|
|
305
|
+
if not release_ids or not component_release_ids:
|
|
306
|
+
return []
|
|
307
|
+
rows = await clickhouse.query(
|
|
308
|
+
_by_release(
|
|
309
|
+
'SELECT DISTINCT c.release_id AS release_id,'
|
|
310
|
+
' c.component_id AS component_id,'
|
|
311
|
+
' c.component_release_id AS component_release_id,'
|
|
312
|
+
' c.version AS version',
|
|
313
|
+
' AND c.component_release_id IN'
|
|
314
|
+
' {component_release_ids:Array(String)}',
|
|
315
|
+
),
|
|
316
|
+
{
|
|
317
|
+
'release_ids': release_ids,
|
|
318
|
+
'component_release_ids': component_release_ids,
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
return [{key: str(value) for key, value in row.items()} for row in rows]
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
async def release_components(
|
|
325
|
+
release_id: str,
|
|
326
|
+
) -> list[dict[str, typing.Any]]:
|
|
327
|
+
"""One release's currently-published component set.
|
|
328
|
+
|
|
329
|
+
Returns the denormalized columns plus the ids the caller hydrates
|
|
330
|
+
the rest from. ``license``, ``supplier``, ``hashes``, and the
|
|
331
|
+
component identifiers are governance attributes of the
|
|
332
|
+
``ComponentRelease`` node and were never copied here, so the
|
|
333
|
+
listing endpoint fetches them from the graph by id -- a bounded
|
|
334
|
+
``IN`` over one release's versions, not a traversal.
|
|
335
|
+
"""
|
|
336
|
+
rows = await clickhouse.query(
|
|
337
|
+
_by_release(
|
|
338
|
+
'SELECT c.component_id AS component_id,'
|
|
339
|
+
' c.component_release_id AS component_release_id,'
|
|
340
|
+
' c.purl_name AS purl_name,'
|
|
341
|
+
' c.ecosystem AS ecosystem,'
|
|
342
|
+
' c.version AS version,'
|
|
343
|
+
' c.scope AS scope,'
|
|
344
|
+
' c.groups AS groups',
|
|
345
|
+
),
|
|
346
|
+
{'release_ids': [release_id]},
|
|
347
|
+
)
|
|
348
|
+
return list(rows)
|