Skip to content

Commit 01ecf07

Browse files
authored
TW-5372 Add application and workspace admin APIs (#475)
1 parent fef8e8c commit 01ecf07

20 files changed

Lines changed: 1173 additions & 125 deletions

.pylintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
good-names=id,k,v,cc,to,ip
33
max-line-length=120
44

5+
[DESIGN]
6+
max-public-methods=25
7+
58
[MESSAGES CONTROL]
69
disable=
710
missing-module-docstring,

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ nylas-python Changelog
22
======================
33
Unreleased
44
----------
5+
* Aligned Lists create support with the public `POST /v3/lists` schema and added create response/schema coverage
6+
* Added Manage Domains service-account auth support with canonical signed request bodies, bearer-auth suppression, encoded domain path segments, and `dmarc`/`arc` verification types
7+
* Added Workspaces resource (`client.workspaces`) with `list`, `find`, `create`, `update` (PATCH), `destroy`, `auto_group`, `manual_assign`, `default`, `policy_id`, and `rule_ids`
8+
* Corrected RedirectUris `update` to use PATCH instead of PUT; added `deleted_at` to the RedirectUri model and made `platform` optional on create
9+
* Verified and extended Applications: added `update` (PATCH `/v3/applications`) and public response fields (`idp_settings`, hosted-authentication legal URLs, `domain`, `blocked`, timestamps)
510
* Fix draft and other JSON API requests failing with "only JSON and multipart supported" by sending `Content-Type: application/json` instead of `application/json; charset=utf-8`
611

712
v6.15.0
813
----------
914
* Added Lists support (`Client.lists`, `/v3/lists`): list, create, find, update, and delete lists, plus `list_items`, `add_items`, and `remove_items` for `/v3/lists/{list_id}/items`, with typed request/response models in `nylas.models.lists`
10-
* Added Manage Domains (`Client.domains`, `/v3/admin/domains`): list, create, find, update, delete, `get_info`, and `verify` with models in `nylas.models.domains`; optional `ServiceAccountSigner` (`nylas.handler.service_account`) for service-account headers (`X-Nylas-Kid`, `X-Nylas-Nonce`, `X-Nylas-Timestamp`, `X-Nylas-Signature`) on each `Domains` method; new `cryptography` dependency, RSA signing, and `HttpClient` `serialized_json_body` so signed payloads match the wire body
15+
* Added Manage Domains (`Client.domains`, `/v3/admin/domains`): list, create, find, update, delete, `get_info`, and `verify` with models in `nylas.models.domains`; optional `ServiceAccountSigner` (`nylas.handler.service_account`) for service-account auth headers (`X-Nylas-Kid`, `X-Nylas-Nonce`, `X-Nylas-Timestamp`, `X-Nylas-Signature`) on each `Domains` method; new `cryptography` dependency, RSA signing, bearer-auth suppression for signed domain requests, and `HttpClient` `serialized_json_body` so signed payloads match the wire body
1116
* Added Transactional Send: `Client.transactional_send.send()` for `POST /v3/domains/{domain_name}/messages/send`, with `TransactionalSendMessageRequest` and `TransactionalTemplate` models (JSON and multipart send behavior aligned with grant `messages.send`)
1217
* Added Policies support (`Client.policies`, `/v3/policies`): list, create, find, update, and delete, with typed request/response models in `nylas.models.policies`
1318
* Added Rules support (`Client.rules`): list, create, find, update, and delete for `/v3/rules`, plus `list_evaluations` for `/v3/grants/{grant_id}/rule-evaluations`, with typed request/response models in `nylas.models.rules`
@@ -548,4 +553,3 @@ Added tests
548553
v0.3.5
549554
------
550555
Drafts can now be sent without an implicit intermediate save to the mail provider.
551-

nylas/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from nylas.resources.scheduler import Scheduler
2121
from nylas.resources.notetakers import Notetakers
2222
from nylas.resources.rules import Rules
23+
from nylas.resources.workspaces import Workspaces
2324

2425

2526
class Client:
@@ -246,3 +247,13 @@ def notetakers(self) -> Notetakers:
246247
The Notetakers API.
247248
"""
248249
return Notetakers(self.http_client)
250+
251+
@property
252+
def workspaces(self) -> Workspaces:
253+
"""
254+
Access the Workspaces API.
255+
256+
Returns:
257+
The Workspaces API.
258+
"""
259+
return Workspaces(self.http_client)

nylas/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ class RequestOverrides(TypedDict):
2222
api_uri: The API URI to use for the request.
2323
timeout: The timeout to use for the request.
2424
headers: Additional headers to include in the request.
25+
skip_auth: Suppress the default bearer Authorization header for endpoints
26+
that use a different authentication mechanism.
2527
"""
2628

2729
api_key: NotRequired[str]
2830
api_uri: NotRequired[str]
2931
timeout: NotRequired[int]
3032
headers: NotRequired[dict]
33+
skip_auth: NotRequired[bool]
3134

3235

3336
DEFAULT_REGION = Region.US

nylas/handler/http_client.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ def _validate_response(response: Response) -> Tuple[Dict, CaseInsensitiveDict]:
2828
or "connect/revoke" in parsed_url.path
2929
):
3030
parsed_error = NylasOAuthErrorResponse.from_dict(response_data)
31-
raise NylasOAuthError(parsed_error, response.status_code, response.headers)
31+
raise NylasOAuthError(
32+
parsed_error, response.status_code, response.headers
33+
)
3234

3335
parsed_error = NylasApiErrorResponse.from_dict(response_data)
3436
raise NylasApiError(parsed_error, response.status_code, response.headers)
@@ -47,6 +49,7 @@ def _validate_response(response: Response) -> Tuple[Dict, CaseInsensitiveDict]:
4749
) from exc
4850
return (response_data, response.headers)
4951

52+
5053
def _build_query_params(base_url: str, query_params: dict = None) -> str:
5154
query_param_parts = []
5255
for key, value in query_params.items():
@@ -107,7 +110,9 @@ def _execute(
107110
if serialized_json_body is not None and data is None:
108111
json_data = serialized_json_body
109112
elif request_body is not None and data is None:
110-
json_data = json.dumps(request_body, ensure_ascii=False, allow_nan=True).encode("utf-8")
113+
json_data = json.dumps(
114+
request_body, ensure_ascii=False, allow_nan=True
115+
).encode("utf-8")
111116
try:
112117
response = requests.request(
113118
request["method"],
@@ -128,7 +133,7 @@ def _execute_download_request(
128133
query_params=None,
129134
stream=False,
130135
overrides=None,
131-
) -> Union[bytes, Response,dict]:
136+
) -> Union[bytes, Response, dict]:
132137
request = self._build_request("GET", path, headers, query_params, overrides)
133138

134139
timeout = self.timeout
@@ -204,8 +209,9 @@ def _build_headers(
204209
headers = {
205210
"X-Nylas-API-Wrapper": "python",
206211
"User-Agent": user_agent_header,
207-
"Authorization": f"Bearer {api_key}",
208212
}
213+
if not (overrides and overrides.get("skip_auth")):
214+
headers["Authorization"] = f"Bearer {api_key}"
209215
if data is not None and data.content_type is not None:
210216
headers["Content-type"] = data.content_type
211217
elif response_body is not None:

nylas/models/application_details.py

Lines changed: 143 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from dataclasses import dataclass, field
2-
from typing import Literal, Optional, List
2+
from typing import Optional, List
33

44
from dataclasses_json import dataclass_json
5+
from typing_extensions import TypedDict, NotRequired
56

67
from nylas.models.redirect_uri import RedirectUri
78

8-
Region = Literal["us", "eu"]
9-
""" Literal representing the available Nylas API regions. """
9+
Region = str
10+
""" The Nylas API region (free-form string, e.g. ``us``, ``eu``). """
1011

11-
Environment = Literal["production", "staging", "development", "sandbox"]
12-
""" Literal representing the different Nylas API environments. """
12+
Environment = str
13+
""" The Nylas API environment (free-form string, e.g. ``sandbox``). """
1314

1415

1516
@dataclass_json
@@ -46,6 +47,8 @@ class HostedAuthentication:
4647
subtitle: Subtitle for the hosted authentication page.
4748
background_color: Background color of the hosted authentication page.
4849
spacing: CSS spacing attribute in px.
50+
terms_of_service_url: URL pointing to the terms of service.
51+
privacy_policy_url: URL pointing to the privacy policy.
4952
"""
5053

5154
background_image_url: Optional[str] = None
@@ -56,6 +59,23 @@ class HostedAuthentication:
5659
subtitle: Optional[str] = None
5760
background_color: Optional[str] = None
5861
spacing: Optional[int] = None
62+
terms_of_service_url: Optional[str] = None
63+
privacy_policy_url: Optional[str] = None
64+
65+
66+
@dataclass_json
67+
@dataclass
68+
class IdpSettings:
69+
"""
70+
Class representation of identity provider settings for the application.
71+
72+
Attributes:
73+
origins: Comma-separated list of allowed origins.
74+
issuers: Comma-separated list of allowed issuers.
75+
"""
76+
77+
origins: Optional[str] = None
78+
issuers: Optional[str] = None
5979

6080

6181
@dataclass_json
@@ -70,14 +90,132 @@ class ApplicationDetails:
7090
region: Region identifier.
7191
environment: Environment identifier.
7292
branding: Branding details for the application.
93+
domain: The white-label domain associated with the application, if any.
7394
hosted_authentication: Hosted authentication branding details.
95+
idp_settings: Identity provider settings.
7496
callback_uris: List of redirect URIs.
97+
created_at: Unix timestamp (seconds) when the application was created.
98+
updated_at: Unix timestamp (seconds) when the application was last updated.
99+
blocked: Whether the application is blocked.
75100
"""
76101

77102
application_id: str
78103
organization_id: str
79104
region: Region
80105
environment: Environment
81106
branding: Branding
107+
domain: Optional[str] = None
82108
hosted_authentication: Optional[HostedAuthentication] = None
109+
idp_settings: Optional[IdpSettings] = None
83110
callback_uris: List[RedirectUri] = field(default_factory=list)
111+
created_at: Optional[int] = None
112+
updated_at: Optional[int] = None
113+
blocked: Optional[bool] = None
114+
115+
116+
class WritableBranding(TypedDict):
117+
"""
118+
Class representing branding details for a create/update application call.
119+
120+
Attributes:
121+
name: Name of the application.
122+
icon_url: URL pointing to the application icon.
123+
website_url: Application/publisher website URL.
124+
description: Description of the application.
125+
"""
126+
127+
name: NotRequired[str]
128+
icon_url: NotRequired[str]
129+
website_url: NotRequired[str]
130+
description: NotRequired[str]
131+
132+
133+
class WritableHostedAuthentication(TypedDict):
134+
"""
135+
Class representing hosted authentication details for a create/update application call.
136+
137+
Attributes:
138+
background_image_url: URL pointing to the background image.
139+
alignment: Alignment of the background image.
140+
color_primary: Primary color of the hosted authentication page.
141+
color_secondary: Secondary color of the hosted authentication page.
142+
title: Title of the hosted authentication page.
143+
subtitle: Subtitle for the hosted authentication page.
144+
background_color: Background color of the hosted authentication page.
145+
spacing: CSS spacing attribute in px.
146+
terms_of_service_url: URL pointing to the terms of service.
147+
privacy_policy_url: URL pointing to the privacy policy.
148+
"""
149+
150+
background_image_url: NotRequired[str]
151+
alignment: NotRequired[str]
152+
color_primary: NotRequired[str]
153+
color_secondary: NotRequired[str]
154+
title: NotRequired[str]
155+
subtitle: NotRequired[str]
156+
background_color: NotRequired[str]
157+
spacing: NotRequired[int]
158+
terms_of_service_url: NotRequired[str]
159+
privacy_policy_url: NotRequired[str]
160+
161+
162+
class WritableIdpSettings(TypedDict):
163+
"""
164+
Class representing identity provider settings for a create/update application call.
165+
166+
Attributes:
167+
origins: Comma-separated list of allowed origins.
168+
issuers: Comma-separated list of allowed issuers.
169+
"""
170+
171+
origins: NotRequired[str]
172+
issuers: NotRequired[str]
173+
174+
175+
class WritableAdditionalSettings(TypedDict):
176+
"""
177+
Class representing additional application settings for an update call.
178+
179+
These settings are write-only: they can be set via the update call but are
180+
stripped from every response and are not bound on the application model.
181+
182+
Attributes:
183+
login_url: The login URL.
184+
logout_url: The logout URL.
185+
refresh_token_expiration_absolute: Absolute refresh token expiration.
186+
refresh_token_expiration_idle: Idle refresh token expiration.
187+
rotate_refresh_token: Whether to rotate the refresh token.
188+
allow_query_param_in_redirect_uri: Whether query params are allowed in redirect URIs.
189+
"""
190+
191+
login_url: NotRequired[str]
192+
logout_url: NotRequired[str]
193+
refresh_token_expiration_absolute: NotRequired[int]
194+
refresh_token_expiration_idle: NotRequired[int]
195+
rotate_refresh_token: NotRequired[bool]
196+
allow_query_param_in_redirect_uri: NotRequired[bool]
197+
198+
199+
class UpdateApplicationRequest(TypedDict):
200+
"""
201+
Class representing a request to update a Nylas application.
202+
203+
Note:
204+
``callback_uris`` / ``redirect_uris`` cannot be set via this request; the
205+
server silently ignores them. Manage callback URIs via the dedicated
206+
redirect-uris endpoints. ``additional_settings`` is write-only and is
207+
stripped from the response.
208+
209+
Attributes:
210+
branding: Branding details for the application.
211+
hosted_authentication: Hosted authentication branding details.
212+
idp_settings: Identity provider settings.
213+
domain: The white-label domain associated with the application.
214+
additional_settings: Additional (write-only) application settings.
215+
"""
216+
217+
branding: NotRequired[WritableBranding]
218+
hosted_authentication: NotRequired[WritableHostedAuthentication]
219+
idp_settings: NotRequired[WritableIdpSettings]
220+
domain: NotRequired[str]
221+
additional_settings: NotRequired[WritableAdditionalSettings]

nylas/models/domains.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22
from typing import Any, Literal, Optional
33

44
from dataclasses_json import config, dataclass_json
5-
from typing_extensions import TypedDict
5+
from typing_extensions import NotRequired, TypedDict
66

77
from nylas.models.list_query_params import ListQueryParams
88

9-
DomainVerificationType = Literal["ownership", "dkim", "spf", "feedback", "mx"]
9+
DomainVerificationRequestType = Literal["ownership", "dkim", "spf", "feedback", "mx"]
10+
DomainVerificationType = Literal[
11+
"ownership", "dkim", "spf", "feedback", "mx", "dmarc", "arc"
12+
]
13+
14+
15+
class DomainVerificationOptions(TypedDict, total=False):
16+
"""Options for domain verification operations."""
17+
18+
key_length: int
1019

1120

1221
class ListDomainsQueryParams(ListQueryParams):
@@ -37,13 +46,15 @@ class UpdateDomainRequest(TypedDict, total=False):
3746
class GetDomainInfoRequest(TypedDict):
3847
"""Request body for retrieving DNS records for a verification type."""
3948

40-
type: DomainVerificationType
49+
type: DomainVerificationRequestType
50+
options: NotRequired[DomainVerificationOptions]
4151

4252

4353
class VerifyDomainRequest(TypedDict):
4454
"""Request body for triggering DNS verification."""
4555

46-
type: DomainVerificationType
56+
type: DomainVerificationRequestType
57+
options: NotRequired[DomainVerificationOptions]
4758

4859

4960
@dataclass_json

nylas/models/lists.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class NylasList:
5050
id: Optional[str] = None
5151
name: Optional[str] = None
5252
description: Optional[str] = None
53-
type: Optional[str] = None
53+
type: Optional[ListType] = None
5454
items_count: Optional[int] = None
5555
application_id: Optional[str] = None
5656
organization_id: Optional[str] = None

nylas/models/redirect_uri.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ class RedirectUri:
3939
url: Redirect URL.
4040
platform: Platform identifier.
4141
settings: Configuration settings.
42+
deleted_at: Soft-delete timestamp (Unix seconds); omitted when not deleted.
4243
"""
4344

4445
id: str
4546
url: str
4647
platform: str
4748
settings: Optional[RedirectUriSettings] = None
49+
deleted_at: Optional[int] = None
4850

4951

5052
class WritableRedirectUriSettings(TypedDict):
@@ -74,12 +76,12 @@ class CreateRedirectUriRequest(TypedDict):
7476
7577
Attributes:
7678
url: Redirect URL.
77-
platform: Platform identifier.
79+
platform: Platform identifier. Optional; defaults to "web" server-side.
7880
settings: Optional settings for the redirect uri.
7981
"""
8082

8183
url: str
82-
platform: str
84+
platform: NotRequired[str]
8385
settings: NotRequired[WritableRedirectUriSettings]
8486

8587

0 commit comments

Comments
 (0)