Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Fetch the endpoint spec first (see Crowdin API reference below). Then:
- List endpoints call `self._get_entire_data(method="get", path=..., params=...)` so `with_fetch_all()` pagination works; everything else calls `self.requester.request(...)`.
- Project-scoped methods take `projectId: Optional[int] = None` and resolve it via `projectId or self.get_project_id()`.
- Request body shapes go in `types.py` as TypedDicts; enum values in `enums.py`. Enums and `Sorting` objects can be passed straight into `params`/`request_data` — the custom JSON encoder serializes them, and `None` values are stripped before sending.
- End the docstring with `Link to documentation:` and the developer.crowdin.com operation URL (pdoc publishes these).
- End the docstring with `Link to documentation:` and the support.crowdin.com operation URL, e.g. `https://support.crowdin.com/developer/api/v2/#operation/api.projects.getMany` (Enterprise: `https://support.crowdin.com/developer/enterprise/api/v2/#operation/...`). pdoc publishes these.
2. For a new resource, register it in three places: an import plus `__all__` entry in `crowdin_api/api_resources/__init__.py` (alphabetical), a `@property` on `CrowdinClient` in `client.py` (copy an existing property; use the enterprise-guard or per-platform variant when the API is Enterprise-only or differs by platform), and one tuple in each of the two parametrize lists in `crowdin_api/tests/test_client.py`. Some resource classes exist but were never registered (e.g. `BranchesResource`, `StringCorrectionsResource`) — "adding" one of those is exactly this registration work.
3. Test in the resource's `tests/` dir: patch the requester with `@mock.patch("crowdin_api.requester.APIRequester.request")`, call the method, then `m_request.assert_called_once_with(method=..., path=..., ...)` with the exact kwargs. The `base_absolut_url` fixture (spelled without the second "e") provides the base URL. No test performs real HTTP.

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Crowdin API is a full-featured RESTful API that helps you to integrate localizat
<div align="center">

[**`API Client Docs`**](https://crowdin.github.io/crowdin-api-client-python/) &nbsp;|&nbsp;
[**`Crowdin API`**](https://developer.crowdin.com/api/v2/) &nbsp;|&nbsp;
[**`Crowdin Enterprise API`**](https://developer.crowdin.com/enterprise/api/v2/)
[**`Crowdin API`**](https://support.crowdin.com/developer/api/v2/) &nbsp;|&nbsp;
[**`Crowdin Enterprise API`**](https://support.crowdin.com/developer/enterprise/api/v2/)

[![PyPI](https://img.shields.io/pypi/v/crowdin-api-client?cacheSeconds=3600)](https://pypi.org/project/crowdin-api-client/)
[![Downloads](https://pepy.tech/badge/crowdin-api-client)](https://pepy.tech/project/crowdin-api-client)
Expand Down Expand Up @@ -185,7 +185,7 @@ class FirstCrowdinClient(CrowdinClient):

### GraphQL API

This library also provides the possibility to use [GraphQL API](https://developer.crowdin.com/graphql-api/):
This library also provides the possibility to use [GraphQL API](https://support.crowdin.com/developer/graphql-api/):

```python
from crowdin_api import CrowdinClient
Expand Down
35 changes: 32 additions & 3 deletions crowdin_api/api_resources/__init__.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,93 @@
from .advisors.resource import AdvisorsResource
from .ai.resource import AIResource, EnterpriseAIResource
from .application.resource import ApplicationResource
from .branches.resource import BranchesResource
from .bundles.resource import BundlesResource
from .clients.resource import ClientsResource
from .custom_placeholders.resource import CustomPlaceholdersResource
from .custom_spellcheckers.resource import CustomSpellcheckersResource
from .dictionaries.resource import DictionariesResource
from .distributions.resource import DistributionsResource
from .external_qa_checks.resource import ExternalQaChecksResource
from .fields.resource import FieldsResource
from .glossaries.resource import GlossariesResource
from .groups.resource import GroupsResource
from .labels.resource import LabelsResource
from .languages.resource import LanguagesResource
from .machine_translation_engines.resource import MachineTranslationEnginesResource
from .machine_translation_engines.resource import (
EnterpriseMachineTranslationEnginesResource,
MachineTranslationEnginesResource,
)
from .notifications.resource import NotificationResource
from .organization.resource import OrganizationResource
from .project_placeholders.resource import ProjectPlaceholdersResource
from .projects.resource import ProjectsResource
from .reports.resource import EnterpriseReportsResource, ReportsResource
from .screenshots.resource import ScreenshotsResource
from .security_logs.resource import SecurityLogsResource
from .source_files.resource import SourceFilesResource
from .source_strings.resource import SourceStringsResource
from .source_strings.resource import EnterpriseSourceStringsResource, SourceStringsResource
from .storages.resource import StoragesResource
from .string_comments.resource import StringCommentsResource
from .string_corrections.resource import StringCorrectionsResource
from .string_translations.resource import StringTranslationsResource
from .style_guides.resource import StyleGuidesResource
from .system_placeholders.resource import SystemPlaceholdersResource
from .tasks.resource import EnterpriseTasksResource, TasksResource
from .teams.resource import TeamsResource
from .translation_memory.resource import TranslationMemoryResource
from .translation_status.resource import TranslationStatusResource
from .translations.resource import TranslationsResource
from .users.resource import EnterpriseUsersResource, UsersResource
from .vendors.resource import VendorsResource
from .webhooks.organization.resource import OrganizationWebhooksResource
from .webhooks.resource import WebhooksResource
from .workflows.resource import WorkflowsResource

__all__ = [
"AdvisorsResource",
"AIResource",
"EnterpriseAIResource",
"ApplicationResource",
"BranchesResource",
"BundlesResource",
"ClientsResource",
"CustomPlaceholdersResource",
"CustomSpellcheckersResource",
"DictionariesResource",
"DistributionsResource",
"ExternalQaChecksResource",
"FieldsResource",
"GlossariesResource",
"GroupsResource",
"LabelsResource",
"LanguagesResource",
"MachineTranslationEnginesResource",
"EnterpriseMachineTranslationEnginesResource",
"NotificationResource",
"OrganizationResource",
"OrganizationWebhooksResource",
"ProjectPlaceholdersResource",
"ProjectsResource",
"ReportsResource",
"EnterpriseReportsResource",
"ScreenshotsResource",
"SecurityLogsResource",
"SourceFilesResource",
"SourceStringsResource",
"EnterpriseSourceStringsResource",
"StoragesResource",
"StringCommentsResource",
"StringCorrectionsResource",
"StringTranslationsResource",
"StyleGuidesResource",
"SystemPlaceholdersResource",
"TasksResource",
"EnterpriseTasksResource",
"TeamsResource",
"TranslationMemoryResource",
"TranslationStatusResource",
"TranslationsResource",
"TranslationStatusResource",
"UsersResource",
"EnterpriseUsersResource",
"VendorsResource",
Expand Down
1 change: 1 addition & 0 deletions crowdin_api/api_resources/advisors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pdoc__ = {'tests': False}
40 changes: 40 additions & 0 deletions crowdin_api/api_resources/advisors/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from enum import Enum


class AdvisorInspectorMode(Enum):
AUTO = "auto"
ALL = "all"


class AdvisorInsightStatus(Enum):
PENDING = "pending"
CHECKING = "checking"
OUTDATED = "outdated"
DONE = "done"


class AdvisorInsightOutcome(Enum):
FLAGGED = "flagged"
CLEAR = "clear"
NOT_APPLICABLE = "not_applicable"


class AdvisorInsightPatchPath(Enum):
IS_DISMISSED = "/isDismissed"


class AdvisorInsightMetricUnit(Enum):
PERCENT = "percent"
COUNT = "count"


class AdvisorInsightMetricTone(Enum):
DEFAULT = "default"
SUCCESS = "success"
DANGER = "danger"


class AdvisorInsightMetricSource(Enum):
DETERMINISTIC = "deterministic"
AI = "ai"
APP = "app"
187 changes: 187 additions & 0 deletions crowdin_api/api_resources/advisors/resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
from datetime import datetime
from typing import Any, Dict, Iterable, Optional, Union

from crowdin_api.api_resources.abstract.resources import BaseResource
from crowdin_api.api_resources.advisors.enums import (
AdvisorInsightOutcome,
AdvisorInsightStatus,
)
from crowdin_api.api_resources.advisors.types import (
AdvisorCheckInspector,
AdvisorInsightMetric,
AdvisorInsightPatchRequest,
AdvisorInsightRecommendation,
)
from crowdin_api.utils import convert_to_query_list


class AdvisorsResource(BaseResource):
"""
Resource for Advisors.

Advisors run inspectors against a project and report insights about its localization health.

Link to documentation:
https://support.crowdin.com/developer/api/v2/#tag/Advisors

Link to documentation for enterprise:
https://support.crowdin.com/developer/enterprise/api/v2/#tag/Advisors
"""

def get_advisor_checks_path(self, projectId: int, checkId: Optional[str] = None):
if checkId is not None:
return f"projects/{projectId}/advisors/checks/{checkId}"
return f"projects/{projectId}/advisors/checks"

def get_advisor_insights_path(self, projectId: int, insightId: Optional[int] = None):
if insightId is not None:
return f"projects/{projectId}/advisors/insights/{insightId}"
return f"projects/{projectId}/advisors/insights"

def create_advisor_check(
self,
category: Optional[str] = None,
inspectors: Optional[Iterable[AdvisorCheckInspector]] = None,
projectId: Optional[int] = None,
):
"""
Create Advisor Check.

At most one of `category` or `inspectors` may be set; if neither is set, every
inspector is re-checked.

Link to documentation:
https://support.crowdin.com/developer/api/v2/#operation/api.projects.advisors.checks.post

Link to documentation for enterprise:
https://support.crowdin.com/developer/enterprise/api/v2/#operation/api.projects.advisors.checks.post
"""
if category is not None and inspectors is not None:
raise ValueError("You can set only one of category or inspectors.")

projectId = projectId or self.get_project_id()

return self.requester.request(
method="post",
path=self.get_advisor_checks_path(projectId=projectId),
request_data={"category": category, "inspectors": inspectors},
)

def get_advisor_check_status(self, checkId: str, projectId: Optional[int] = None):
"""
Get Advisor Check Status.

Link to documentation:
https://support.crowdin.com/developer/api/v2/#operation/api.projects.advisors.checks.get

Link to documentation for enterprise:
https://support.crowdin.com/developer/enterprise/api/v2/#operation/api.projects.advisors.checks.get
"""
projectId = projectId or self.get_project_id()

return self.requester.request(
method="get",
path=self.get_advisor_checks_path(projectId=projectId, checkId=checkId),
)

def list_advisor_insights(
self,
projectId: Optional[int] = None,
isDismissed: Optional[bool] = None,
status: Optional[
Union[str, AdvisorInsightStatus, Iterable[Union[str, AdvisorInsightStatus]]]
] = None,
outcome: Optional[
Union[str, AdvisorInsightOutcome, Iterable[Union[str, AdvisorInsightOutcome]]]
] = None,
offset: Optional[int] = None,
limit: Optional[int] = None,
):
"""
List Advisor Insights.

`status` and `outcome` accept a single value or a list of values (sent comma-separated).

Link to documentation:
https://support.crowdin.com/developer/api/v2/#operation/api.projects.advisors.insights.getMany

Link to documentation for enterprise:
https://support.crowdin.com/developer/enterprise/api/v2/#operation/api.projects.advisors.insights.getMany
"""
projectId = projectId or self.get_project_id()
params = {
"isDismissed": isDismissed,
"status": convert_to_query_list(status),
"outcome": convert_to_query_list(outcome),
}
params.update(self.get_page_params(offset=offset, limit=limit))

return self._get_entire_data(
method="get",
path=self.get_advisor_insights_path(projectId=projectId),
params=params,
)

def edit_advisor_insight(
self,
insightId: int,
data: Iterable[AdvisorInsightPatchRequest],
projectId: Optional[int] = None,
):
"""
Edit Advisor Insight.

Currently only `/isDismissed` is patchable.

Link to documentation:
https://support.crowdin.com/developer/api/v2/#operation/api.projects.advisors.insights.patch

Link to documentation for enterprise:
https://support.crowdin.com/developer/enterprise/api/v2/#operation/api.projects.advisors.insights.patch
"""
projectId = projectId or self.get_project_id()

return self.requester.request(
method="patch",
path=self.get_advisor_insights_path(projectId=projectId, insightId=insightId),
request_data=data,
)

def create_or_update_application_advisor_insight(
self,
applicationIdentifier: str,
moduleKey: str,
outcome: AdvisorInsightOutcome,
checkedAt: Optional[Union[datetime, str]] = None,
metrics: Optional[Iterable[AdvisorInsightMetric]] = None,
recommendations: Optional[Iterable[AdvisorInsightRecommendation]] = None,
payload: Optional[Dict[str, Any]] = None,
projectId: Optional[int] = None,
):
"""
Create or Update Application Advisor Insight.

Called by an installed application's `advisor-inspector` module to publish its check result.

Link to documentation:
https://support.crowdin.com/developer/api/v2/#operation/api.projects.applications.modules.advisors.insights.put

Link to documentation for enterprise:
https://support.crowdin.com/developer/enterprise/api/v2/#operation/api.projects.applications.modules.advisors.insights.put
"""
projectId = projectId or self.get_project_id()

return self.requester.request(
method="put",
path=(
f"projects/{projectId}/applications/{applicationIdentifier}"
f"/modules/{moduleKey}/advisors/insights"
),
request_data={
"outcome": outcome,
"checkedAt": checkedAt,
"metrics": metrics,
"recommendations": recommendations,
"payload": payload,
},
)
Loading
Loading