fluidattacks_zoho_sdk 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,8 @@
1
+ from fluidattacks_zoho_sdk.auth import AuthApiFactory, Credentials
2
+
3
+ __version__ = "1.0.0"
4
+
5
+ __all__ = [
6
+ "AuthApiFactory",
7
+ "Credentials",
8
+ ]
@@ -0,0 +1,177 @@
1
+ from typing import (
2
+ IO,
3
+ )
4
+
5
+ from fa_purity import Maybe, ResultE, Unsafe
6
+ from fa_purity.date_time import DatetimeUTC
7
+ from fa_purity.json import (
8
+ JsonObj,
9
+ JsonPrimitiveUnfolder,
10
+ JsonUnfolder,
11
+ UnfoldedFactory,
12
+ Unfolder,
13
+ )
14
+ from fluidattacks_etl_utils.decode import DecodeUtils
15
+ from fluidattacks_etl_utils.natural import Natural
16
+
17
+ from fluidattacks_zoho_sdk.auth import Credentials
18
+ from fluidattacks_zoho_sdk.ids import (
19
+ AccountId,
20
+ ContactId,
21
+ CrmId,
22
+ DeparmentId,
23
+ ProductId,
24
+ ProfileId,
25
+ RoleId,
26
+ TeamId,
27
+ TicketId,
28
+ UserId,
29
+ )
30
+
31
+
32
+ def _decode_zoho_creds(raw: JsonObj) -> ResultE[Credentials]:
33
+ client_id = JsonUnfolder.require(raw, "client_id", Unfolder.to_primitive).bind(
34
+ JsonPrimitiveUnfolder.to_str,
35
+ )
36
+ client_secret = JsonUnfolder.require(raw, "client_secret", Unfolder.to_primitive).bind(
37
+ JsonPrimitiveUnfolder.to_str,
38
+ )
39
+ refresh_token = JsonUnfolder.require(raw, "refresh_token", Unfolder.to_primitive).bind(
40
+ JsonPrimitiveUnfolder.to_str,
41
+ )
42
+ scopes_result = JsonUnfolder.require(
43
+ raw,
44
+ "scopes",
45
+ lambda i: Unfolder.to_list_of(
46
+ i,
47
+ lambda x: Unfolder.to_primitive(x).bind(JsonPrimitiveUnfolder.to_str),
48
+ ),
49
+ )
50
+ return client_id.bind(
51
+ lambda cid: client_secret.bind(
52
+ lambda secret: refresh_token.bind(
53
+ lambda token: scopes_result.map(
54
+ lambda scopes: Credentials(cid, secret, token, frozenset(scopes)),
55
+ ),
56
+ ),
57
+ ),
58
+ )
59
+
60
+
61
+ def get_sub_json(raw: JsonObj, key: str) -> JsonObj:
62
+ return (
63
+ JsonUnfolder.require(raw, key, lambda v: Unfolder.to_json(v))
64
+ .alt(Unsafe.raise_exception)
65
+ .to_union()
66
+ )
67
+
68
+
69
+ def decode_optional_date(raw: JsonObj, key: str) -> ResultE[Maybe[DatetimeUTC]]:
70
+ return JsonUnfolder.optional(raw, key, DecodeUtils.to_opt_date_time).map(
71
+ lambda v: v.bind(lambda j: j),
72
+ )
73
+
74
+
75
+ def decode_require_date(raw: JsonObj, key: str) -> ResultE[DatetimeUTC]:
76
+ return JsonUnfolder.require(raw, key, DecodeUtils.to_date_time)
77
+
78
+
79
+ def decode_maybe_str(raw: JsonObj, key: str) -> ResultE[Maybe[str]]:
80
+ return JsonUnfolder.optional(raw, key, DecodeUtils.to_opt_str).map(
81
+ lambda v: v.bind(lambda j: j),
82
+ )
83
+
84
+
85
+ def decode_zoho_creds(auth_file: IO[str]) -> ResultE[Credentials]:
86
+ return UnfoldedFactory.load(auth_file).bind(_decode_zoho_creds)
87
+
88
+
89
+ def decode_user_id(raw: JsonObj) -> ResultE[UserId]:
90
+ return (
91
+ JsonUnfolder.require(raw, "id", DecodeUtils.to_str)
92
+ .bind(lambda v: Natural.from_int(int(v)))
93
+ .map(UserId)
94
+ )
95
+
96
+
97
+ def decode_rol_id(raw: JsonObj) -> ResultE[RoleId]:
98
+ return (
99
+ JsonUnfolder.require(raw, "roleId", DecodeUtils.to_str)
100
+ .bind(lambda v: Natural.from_int(int(v)))
101
+ .map(RoleId)
102
+ )
103
+
104
+
105
+ def decode_profile_id(raw: JsonObj) -> ResultE[ProfileId]:
106
+ return (
107
+ JsonUnfolder.require(raw, "profileId", DecodeUtils.to_str)
108
+ .bind(lambda v: Natural.from_int(int(v)))
109
+ .map(ProfileId)
110
+ )
111
+
112
+
113
+ def decode_account_id(raw: JsonObj) -> ResultE[AccountId]:
114
+ return (
115
+ JsonUnfolder.require(raw, "accountId", DecodeUtils.to_str)
116
+ .bind(lambda v: Natural.from_int(int(v)))
117
+ .map(AccountId)
118
+ )
119
+
120
+
121
+ def decode_crm_id(raw: JsonObj) -> ResultE[CrmId]:
122
+ return (
123
+ JsonUnfolder.require(get_sub_json(raw, "zohoCRMContact"), "id", DecodeUtils.to_str)
124
+ .bind(lambda v: Natural.from_int(int(v)))
125
+ .map(lambda obj: CrmId(obj))
126
+ )
127
+
128
+
129
+ def decode_deparment_id(raw: JsonObj) -> ResultE[DeparmentId]:
130
+ return (
131
+ JsonUnfolder.optional(raw, "departmentId", DecodeUtils.to_opt_str)
132
+ .map(
133
+ lambda v: v.bind(lambda x: x).map(
134
+ lambda j: Natural.from_int(int(j)).alt(Unsafe.raise_exception).to_union(),
135
+ ),
136
+ )
137
+ .map(lambda obj: DeparmentId(obj))
138
+ )
139
+
140
+
141
+ def decode_product_id(raw: JsonObj) -> ResultE[ProductId]:
142
+ return (
143
+ JsonUnfolder.optional(raw, "productId", DecodeUtils.to_opt_str)
144
+ .map(
145
+ lambda v: v.bind(lambda x: x).map(
146
+ lambda j: Natural.from_int(int(j)).alt(Unsafe.raise_exception).to_union(),
147
+ ),
148
+ )
149
+ .map(lambda obj: ProductId(obj))
150
+ )
151
+
152
+
153
+ def decode_team_id(raw: JsonObj) -> ResultE[TeamId]:
154
+ return JsonUnfolder.require(raw, "teamId", DecodeUtils.to_str).bind(
155
+ lambda v: Natural.from_int(int(v)).map(lambda obj: TeamId(obj)),
156
+ )
157
+
158
+
159
+ def decode_ticket_id(raw: JsonObj) -> ResultE[TicketId]:
160
+ return JsonUnfolder.require(raw, "id", DecodeUtils.to_str).bind(
161
+ lambda v: Natural.from_int(int(v)).map(lambda obj: TicketId(obj)),
162
+ )
163
+
164
+
165
+ def decode_contact_id(raw: JsonObj) -> ResultE[ContactId]:
166
+ return JsonUnfolder.require(get_sub_json(raw, "contact"), "id", DecodeUtils.to_str).bind(
167
+ lambda v: Natural.from_int(int(v)).map(lambda obj: ContactId(obj)),
168
+ )
169
+
170
+
171
+ def decode_account_id_ticket(raw: JsonObj) -> ResultE[AccountId]:
172
+ account_obj = get_sub_json(raw, "contact")
173
+ return JsonUnfolder.require(
174
+ get_sub_json(account_obj, "account"),
175
+ "id",
176
+ DecodeUtils.to_str,
177
+ ).bind(lambda v: Natural.from_int(int(v)).map(lambda obj: AccountId(obj)))
@@ -0,0 +1,51 @@
1
+ import logging
2
+ from dataclasses import (
3
+ dataclass,
4
+ )
5
+
6
+ from fa_purity import (
7
+ Cmd,
8
+ )
9
+ from fa_purity.json import (
10
+ JsonObj,
11
+ )
12
+
13
+ from . import (
14
+ _access,
15
+ _refresh,
16
+ _revoke,
17
+ )
18
+ from ._core import (
19
+ Credentials,
20
+ RefreshToken,
21
+ Token,
22
+ )
23
+
24
+ ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
25
+ LOG = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class AuthApi:
30
+ new_access_token: Cmd[Token]
31
+ manual_new_refresh_token: Cmd[RefreshToken]
32
+ manual_revoke_token: Cmd[JsonObj]
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class AuthApiFactory:
37
+ @staticmethod
38
+ def auth_api(creds: Credentials) -> AuthApi:
39
+ return AuthApi(
40
+ _access.new_access_token(creds),
41
+ _refresh.generate_refresh_token(creds),
42
+ _revoke.revoke_refresh_token(),
43
+ )
44
+
45
+
46
+ __version__ = "1.0.0"
47
+ __all__ = [
48
+ "Credentials",
49
+ "RefreshToken",
50
+ "Token",
51
+ ]
@@ -0,0 +1,86 @@
1
+ import inspect
2
+ import logging
3
+
4
+ from fa_purity import (
5
+ Cmd,
6
+ FrozenDict,
7
+ ResultE,
8
+ cast_exception,
9
+ )
10
+ from fa_purity.json import (
11
+ JsonObj,
12
+ JsonPrimitiveUnfolder,
13
+ JsonUnfolder,
14
+ Primitive,
15
+ UnfoldedFactory,
16
+ Unfolder,
17
+ )
18
+ from fluidattacks_etl_utils.bug import Bug
19
+ from pure_requests.basic import (
20
+ Data,
21
+ Endpoint,
22
+ HttpClientFactory,
23
+ Params,
24
+ )
25
+
26
+ from ._core import (
27
+ Credentials,
28
+ Token,
29
+ )
30
+ from ._decode import (
31
+ decode_json,
32
+ )
33
+
34
+ ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
35
+ LOG = logging.getLogger(__name__)
36
+
37
+
38
+ def _decode(raw: JsonObj) -> ResultE[Token]:
39
+ return JsonUnfolder.require(
40
+ raw,
41
+ "access_token",
42
+ lambda v: Unfolder.to_primitive(v).bind(JsonPrimitiveUnfolder.to_str),
43
+ ).map(Token)
44
+
45
+
46
+ def new_access_token(credentials: Credentials) -> Cmd[Token]:
47
+ LOG.info("Generating access token")
48
+ endpoint = Endpoint(f"{ACCOUNTS_URL}/oauth/v2/token")
49
+ params: dict[str, Primitive] = {
50
+ "refresh_token": credentials.refresh_token,
51
+ "client_id": credentials.client_id,
52
+ "client_secret": credentials.client_secret,
53
+ "grant_type": "refresh_token",
54
+ }
55
+ empty: JsonObj = FrozenDict({})
56
+ client = HttpClientFactory.new_client(None, None, None)
57
+ response = client.post(endpoint, Params(UnfoldedFactory.from_dict(params)), Data(empty)).map(
58
+ lambda r: Bug.assume_success(
59
+ "NewTokenError.response",
60
+ inspect.currentframe(),
61
+ (str(client), endpoint.raw, str(params)),
62
+ r.alt(cast_exception),
63
+ ),
64
+ )
65
+ data = response.map(
66
+ lambda r: Bug.assume_success(
67
+ "NewTokenError.decode_json",
68
+ inspect.currentframe(),
69
+ (str(client), endpoint.raw, str(params)),
70
+ decode_json(r).alt(
71
+ lambda c: c.map(
72
+ cast_exception,
73
+ lambda v: v.map(cast_exception, cast_exception),
74
+ ),
75
+ ),
76
+ ),
77
+ )
78
+
79
+ return data.map(
80
+ lambda j: Bug.assume_success(
81
+ "NewTokenError.decode_json",
82
+ inspect.currentframe(),
83
+ (JsonUnfolder.dumps(j),),
84
+ _decode(j),
85
+ ),
86
+ )
@@ -0,0 +1,39 @@
1
+ import hashlib
2
+ from dataclasses import (
3
+ dataclass,
4
+ )
5
+
6
+ ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class Token:
11
+ raw_token: str
12
+
13
+ def __repr__(self) -> str:
14
+ return "[masked]"
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class RefreshToken:
19
+ raw_token: str
20
+
21
+ def __repr__(self) -> str:
22
+ signature = hashlib.sha256(self.raw_token.encode("utf-8")).hexdigest()[:128]
23
+ return f"[masked] signature={signature}"
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Credentials:
28
+ client_id: str
29
+ client_secret: str
30
+ refresh_token: str
31
+ scopes: frozenset[str]
32
+
33
+ def __repr__(self) -> str:
34
+ return f"Creds(client_id={self.client_id}, scopes={self.scopes})"
35
+
36
+
37
+ __all__ = [
38
+ "Token",
39
+ ]
@@ -0,0 +1,62 @@
1
+ from typing import (
2
+ TypeVar,
3
+ )
4
+
5
+ from fa_purity import (
6
+ Coproduct,
7
+ CoproductFactory,
8
+ FrozenList,
9
+ Result,
10
+ ResultE,
11
+ ResultFactory,
12
+ )
13
+ from fa_purity.json import (
14
+ JsonObj,
15
+ )
16
+ from pure_requests.response import (
17
+ handle_status,
18
+ json_decode,
19
+ )
20
+ from requests import (
21
+ HTTPError,
22
+ JSONDecodeError,
23
+ Response,
24
+ )
25
+
26
+ _T = TypeVar("_T")
27
+
28
+
29
+ def decode_json(
30
+ response: Response,
31
+ ) -> Result[JsonObj, Coproduct[HTTPError, Coproduct[JSONDecodeError, TypeError]]]:
32
+ _factory: CoproductFactory[HTTPError, Coproduct[JSONDecodeError, TypeError]] = (
33
+ CoproductFactory()
34
+ )
35
+ _factory_2: CoproductFactory[JSONDecodeError, TypeError] = CoproductFactory()
36
+ _factory_3: ResultFactory[
37
+ JsonObj,
38
+ Coproduct[HTTPError, Coproduct[JSONDecodeError, TypeError]],
39
+ ] = ResultFactory()
40
+ return (
41
+ handle_status(response)
42
+ .alt(lambda x: _factory.inl(x))
43
+ .bind(
44
+ lambda r: json_decode(r)
45
+ .alt(lambda e: _factory.inr(_factory_2.inl(e)))
46
+ .bind(
47
+ lambda c: c.map(
48
+ lambda j: _factory_3.success(j),
49
+ lambda _: _factory_3.failure(
50
+ _factory.inr(_factory_2.inr(TypeError("Expected non-list"))),
51
+ ),
52
+ ),
53
+ ),
54
+ )
55
+ )
56
+
57
+
58
+ def require_index(items: FrozenList[_T], index: int) -> ResultE[_T]:
59
+ try:
60
+ return Result.success(items[index])
61
+ except IndexError as err:
62
+ return Result.failure(Exception(err))
@@ -0,0 +1,123 @@
1
+ import inspect
2
+ import logging
3
+ from getpass import (
4
+ getpass,
5
+ )
6
+ from urllib.parse import (
7
+ urlencode,
8
+ )
9
+
10
+ from fa_purity import (
11
+ Cmd,
12
+ FrozenDict,
13
+ Result,
14
+ ResultE,
15
+ cast_exception,
16
+ )
17
+ from fa_purity.json import (
18
+ JsonObj,
19
+ JsonPrimitiveUnfolder,
20
+ JsonUnfolder,
21
+ Primitive,
22
+ UnfoldedFactory,
23
+ Unfolder,
24
+ )
25
+ from fluidattacks_etl_utils.bug import Bug
26
+ from pure_requests.basic import (
27
+ Data,
28
+ Endpoint,
29
+ HttpClientFactory,
30
+ Params,
31
+ )
32
+ from requests import (
33
+ RequestException,
34
+ Response,
35
+ )
36
+
37
+ from ._core import (
38
+ ACCOUNTS_URL,
39
+ Credentials,
40
+ RefreshToken,
41
+ )
42
+ from ._decode import (
43
+ decode_json,
44
+ )
45
+
46
+ LOG = logging.getLogger(__name__)
47
+
48
+
49
+ def _decode(raw: JsonObj) -> ResultE[RefreshToken]:
50
+ LOG.debug("raw token response: %s", JsonUnfolder.dumps(raw))
51
+ return JsonUnfolder.require(
52
+ raw,
53
+ "refresh_token",
54
+ lambda v: Unfolder.to_primitive(v).bind(JsonPrimitiveUnfolder.to_str),
55
+ ).map(RefreshToken)
56
+
57
+
58
+ def _user_input_code() -> Cmd[str]:
59
+ def _action() -> str:
60
+ LOG.info("Paste grant token:")
61
+ token = getpass()
62
+ LOG.debug(token)
63
+ return token
64
+
65
+ return Cmd.wrap_impure(_action)
66
+
67
+
68
+ def _gen_refresh_token(
69
+ credentials: Credentials,
70
+ code: str,
71
+ ) -> Cmd[Result[Response, RequestException]]:
72
+ endpoint = Endpoint(f"{ACCOUNTS_URL}/oauth/v2/token")
73
+ raw: dict[str, Primitive] = {
74
+ "grant_type": "authorization_code",
75
+ "client_id": credentials.client_id,
76
+ "client_secret": credentials.client_secret,
77
+ "code": code,
78
+ }
79
+ empty: JsonObj = FrozenDict({})
80
+ encoded = urlencode(raw)
81
+ headers: dict[str, Primitive] = {
82
+ "Content-Type": "application/x-www-form-urlencoded",
83
+ "Content-Length": str(len(encoded)),
84
+ }
85
+ client = HttpClientFactory.new_client(None, UnfoldedFactory.from_dict(headers), None)
86
+ return client.post(endpoint, Params(empty), Data(encoded))
87
+
88
+
89
+ def _decode_refresh(result: Result[Response, RequestException]) -> ResultE[RefreshToken]:
90
+ response = Bug.assume_success(
91
+ "AuthRefreshError.response",
92
+ inspect.currentframe(),
93
+ (),
94
+ result.alt(cast_exception),
95
+ )
96
+ json_data = Bug.assume_success(
97
+ "AuthRefreshError.decode_json",
98
+ inspect.currentframe(),
99
+ (),
100
+ decode_json(response).alt(
101
+ lambda e: e.map(cast_exception, lambda f: f.map(cast_exception, cast_exception)),
102
+ ),
103
+ )
104
+ return _decode(json_data)
105
+
106
+
107
+ def generate_refresh_token(
108
+ credentials: Credentials,
109
+ ) -> Cmd[RefreshToken]:
110
+ msg = Cmd.wrap_impure(
111
+ lambda: LOG.info(
112
+ "Generating refresh token with scopes: %s",
113
+ ",".join(credentials.scopes),
114
+ ),
115
+ )
116
+ return msg + _user_input_code().bind(lambda c: _gen_refresh_token(credentials, c)).map(
117
+ lambda r: Bug.assume_success(
118
+ "AuthRefreshError.decode_token",
119
+ inspect.currentframe(),
120
+ (str(r),),
121
+ _decode_refresh(r),
122
+ ),
123
+ )
@@ -0,0 +1,98 @@
1
+ import inspect
2
+ import logging
3
+ from getpass import (
4
+ getpass,
5
+ )
6
+ from urllib.parse import (
7
+ urlencode,
8
+ )
9
+
10
+ from fa_purity import (
11
+ Cmd,
12
+ FrozenDict,
13
+ cast_exception,
14
+ )
15
+ from fa_purity.json import (
16
+ JsonObj,
17
+ Primitive,
18
+ UnfoldedFactory,
19
+ )
20
+ from fluidattacks_etl_utils.bug import Bug
21
+ from pure_requests.basic import (
22
+ Data,
23
+ Endpoint,
24
+ HttpClientFactory,
25
+ Params,
26
+ )
27
+
28
+ from ._core import (
29
+ RefreshToken,
30
+ )
31
+ from ._decode import (
32
+ decode_json,
33
+ )
34
+
35
+ ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
36
+ LOG = logging.getLogger(__name__)
37
+
38
+
39
+ def _user_input() -> Cmd[RefreshToken]:
40
+ def _action() -> RefreshToken:
41
+ LOG.info("Refresh token to revoke:")
42
+ return RefreshToken(getpass())
43
+
44
+ return Cmd.wrap_impure(_action)
45
+
46
+
47
+ def _revoke_refresh_token(token: RefreshToken) -> Cmd[JsonObj]:
48
+ endpoint = Endpoint(f"{ACCOUNTS_URL}/oauth/v2/token/revoke")
49
+ empty: JsonObj = FrozenDict({})
50
+ raw: dict[str, Primitive] = {
51
+ "token": token.raw_token,
52
+ }
53
+ encoded = urlencode(raw)
54
+ headers: dict[str, Primitive] = {
55
+ "Content-Type": "application/x-www-form-urlencoded",
56
+ "Content-Length": str(len(encoded)),
57
+ }
58
+ client = HttpClientFactory.new_client(None, UnfoldedFactory.from_dict(headers), None)
59
+ return (
60
+ client.post(
61
+ endpoint,
62
+ Params(empty),
63
+ Data(encoded),
64
+ )
65
+ .map(
66
+ lambda r: Bug.assume_success(
67
+ "AuthRevokeError.response",
68
+ inspect.currentframe(),
69
+ (endpoint.raw, str(token)),
70
+ r.alt(cast_exception),
71
+ ),
72
+ )
73
+ .map(
74
+ lambda r: Bug.assume_success(
75
+ "AuthRevokeError.decode",
76
+ inspect.currentframe(),
77
+ (endpoint.raw, str(token), str(r), str(decode_json(r))),
78
+ decode_json(r).alt(
79
+ lambda e: e.map(
80
+ cast_exception,
81
+ lambda x: x.map(cast_exception, cast_exception),
82
+ ),
83
+ ),
84
+ ),
85
+ )
86
+ )
87
+
88
+
89
+ def revoke_refresh_token() -> Cmd[JsonObj]:
90
+ return (
91
+ _user_input()
92
+ .bind(_revoke_refresh_token)
93
+ .bind(
94
+ lambda j: Cmd.wrap_impure(lambda: LOG.debug("revoke_refresh_token: %s", j)).map(
95
+ lambda _: j,
96
+ ),
97
+ )
98
+ )
@@ -0,0 +1,59 @@
1
+ from dataclasses import dataclass
2
+
3
+ from fa_purity import Maybe
4
+ from fluidattacks_etl_utils.natural import Natural
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class UserId:
9
+ user_id: Natural
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class AccountId:
14
+ accound_id: Natural
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class CrmId:
19
+ crm_id: Natural
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class DeparmentId:
24
+ id_deparment: Maybe[Natural]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class TeamId:
29
+ id_team: Natural
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class AgentId:
34
+ agent_id: Natural
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ProfileId:
39
+ id_profile: Natural
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class RoleId:
44
+ id_role: Natural
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class ProductId:
49
+ id_product: Maybe[Natural]
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class TicketId:
54
+ id_ticket: Natural
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ContactId:
59
+ id_contact: Natural
File without changes
File without changes
@@ -0,0 +1,192 @@
1
+ from fa_purity import ResultE
2
+ from fa_purity.json import (
3
+ JsonObj,
4
+ JsonUnfolder,
5
+ )
6
+ from fluidattacks_etl_utils import smash
7
+ from fluidattacks_etl_utils.decode import DecodeUtils
8
+ from fluidattacks_etl_utils.natural import Natural
9
+
10
+ from fluidattacks_zoho_sdk._decoders import (
11
+ decode_account_id,
12
+ decode_account_id_ticket,
13
+ decode_contact_id,
14
+ decode_crm_id,
15
+ decode_deparment_id,
16
+ decode_maybe_str,
17
+ decode_optional_date,
18
+ decode_product_id,
19
+ decode_profile_id,
20
+ decode_require_date,
21
+ decode_rol_id,
22
+ decode_team_id,
23
+ decode_ticket_id,
24
+ decode_user_id,
25
+ )
26
+ from fluidattacks_zoho_sdk.ids import UserId
27
+ from fluidattacks_zoho_sdk.zoho_desk.core import (
28
+ AgentObj,
29
+ ContactAddres,
30
+ ContactDates,
31
+ ContactInfo,
32
+ ContactObj,
33
+ TicketDates,
34
+ TicketObj,
35
+ TicketProperties,
36
+ UserObj,
37
+ )
38
+
39
+
40
+ def decode_user(raw: JsonObj) -> ResultE[UserObj]:
41
+ return smash.smash_result_3(
42
+ decode_user_id(raw),
43
+ JsonUnfolder.require(raw, "firstName", DecodeUtils.to_str),
44
+ JsonUnfolder.require(raw, "lastName", DecodeUtils.to_str),
45
+ ).map(lambda obj: UserObj(*obj))
46
+
47
+
48
+ def decode_agent(raw: JsonObj) -> ResultE[AgentObj]:
49
+ values = smash.smash_result_5(
50
+ decode_user(raw),
51
+ JsonUnfolder.require(raw, "isConfirmed", DecodeUtils.to_bool),
52
+ JsonUnfolder.require(raw, "emailId", DecodeUtils.to_str),
53
+ decode_profile_id(raw),
54
+ decode_rol_id(raw),
55
+ )
56
+
57
+ return (
58
+ smash.bind_chain(
59
+ values,
60
+ lambda vals: JsonUnfolder.require(raw, "status", DecodeUtils.to_str).map(
61
+ lambda status: (*vals, status),
62
+ ),
63
+ )
64
+ .alt(lambda v: v.map(lambda le: le, lambda r: r))
65
+ .map(lambda obj: AgentObj(*obj))
66
+ )
67
+
68
+
69
+ def decode_contact_addres(raw: JsonObj) -> ResultE[ContactAddres]:
70
+ return smash.smash_result_3(
71
+ JsonUnfolder.optional(raw, "city", DecodeUtils.to_opt_str).map(
72
+ lambda v: v.bind(lambda x: x),
73
+ ),
74
+ JsonUnfolder.optional(raw, "country", DecodeUtils.to_opt_str).map(
75
+ lambda v: v.bind(lambda x: x),
76
+ ),
77
+ JsonUnfolder.optional(raw, "street", DecodeUtils.to_opt_str).map(
78
+ lambda v: v.bind(lambda x: x),
79
+ ),
80
+ ).map(lambda obj: ContactAddres(*obj))
81
+
82
+
83
+ def decode_contact_dates(raw: JsonObj) -> ResultE[ContactDates]:
84
+ return smash.smash_result_2(
85
+ JsonUnfolder.require(raw, "createdTime", DecodeUtils.to_date_time),
86
+ JsonUnfolder.require(raw, "modifiedTime", DecodeUtils.to_date_time),
87
+ ).map(lambda obj: ContactDates(*obj))
88
+
89
+
90
+ def decode_contact_info(raw: JsonObj) -> ResultE[ContactInfo]:
91
+ return smash.smash_result_5(
92
+ JsonUnfolder.require(raw, "email", DecodeUtils.to_str),
93
+ JsonUnfolder.optional(raw, "facebook", DecodeUtils.to_opt_str).map(
94
+ lambda v: v.bind(lambda x: x),
95
+ ),
96
+ JsonUnfolder.require(raw, "phone", DecodeUtils.to_str),
97
+ JsonUnfolder.optional(raw, "mobile", DecodeUtils.to_opt_str).map(
98
+ lambda v: v.bind(lambda x: x),
99
+ ),
100
+ JsonUnfolder.optional(raw, "secondaryEmail", DecodeUtils.to_opt_str).map(
101
+ lambda v: v.bind(lambda x: x),
102
+ ),
103
+ ).map(lambda obj: ContactInfo(*obj))
104
+
105
+
106
+ def decode_contact_obj(raw: JsonObj) -> ResultE[ContactObj]:
107
+ first = smash.smash_result_5(
108
+ decode_user(raw),
109
+ decode_contact_info(raw),
110
+ decode_contact_addres(raw),
111
+ decode_contact_dates(raw),
112
+ decode_account_id(raw),
113
+ )
114
+
115
+ second = smash.smash_result_4(
116
+ JsonUnfolder.require(raw, "ownerId", DecodeUtils.to_str)
117
+ .bind(lambda v: Natural.from_int(int(v)))
118
+ .map(lambda j: UserId(j)),
119
+ decode_crm_id(raw),
120
+ JsonUnfolder.optional(raw, "description", DecodeUtils.to_opt_str).map(
121
+ lambda v: v.bind(lambda j: j),
122
+ ),
123
+ JsonUnfolder.optional(raw, "state", DecodeUtils.to_opt_str).map(
124
+ lambda v: v.bind(lambda j: j),
125
+ ),
126
+ )
127
+
128
+ three = smash.smash_result_3(
129
+ JsonUnfolder.optional(raw, "title", DecodeUtils.to_opt_str).map(
130
+ lambda v: v.bind(lambda j: j),
131
+ ),
132
+ JsonUnfolder.optional(raw, "type", DecodeUtils.to_opt_str).map(
133
+ lambda v: v.bind(lambda j: j),
134
+ ),
135
+ JsonUnfolder.optional(raw, "zip", DecodeUtils.to_opt_str).map(
136
+ lambda v: v.bind(lambda j: j),
137
+ ),
138
+ )
139
+
140
+ return smash.smash_result_3(first, second, three).map(lambda v: ContactObj(*v[0], *v[1], *v[2]))
141
+
142
+
143
+ def decode_ticket_dates(raw: JsonObj) -> ResultE[TicketDates]:
144
+ return smash.smash_result_4(
145
+ decode_optional_date(raw, "modifiedTime"),
146
+ decode_require_date(raw, "createdTime"),
147
+ decode_optional_date(raw, "closedTime"),
148
+ decode_optional_date(raw, "customerResponseTime"),
149
+ ).map(lambda v: TicketDates(*v))
150
+
151
+
152
+ def decode_ticket_properties(raw: JsonObj) -> ResultE[TicketProperties]:
153
+ first = smash.smash_result_5(
154
+ decode_ticket_id(raw),
155
+ JsonUnfolder.require(raw, "ticketNumber", DecodeUtils.to_str),
156
+ decode_maybe_str(raw, "subject"),
157
+ decode_maybe_str(raw, "channel"),
158
+ decode_maybe_str(raw, "status"),
159
+ )
160
+
161
+ second = smash.smash_result_4(
162
+ decode_maybe_str(raw, "category"),
163
+ JsonUnfolder.require(raw, "isEscalated", DecodeUtils.to_bool),
164
+ JsonUnfolder.require(raw, "priority", DecodeUtils.to_str),
165
+ decode_maybe_str(raw, "resolution"),
166
+ )
167
+
168
+ three = smash.smash_result_2(
169
+ decode_maybe_str(raw, "clasification"),
170
+ decode_maybe_str(raw, "description"),
171
+ )
172
+ return smash.smash_result_3(first, second, three).map(
173
+ lambda v: TicketProperties(*v[0], *v[1], *v[2]),
174
+ )
175
+
176
+
177
+ def decode_ticket_obj(raw: JsonObj) -> ResultE[TicketObj]:
178
+ first = smash.smash_result_5(
179
+ decode_account_id_ticket(raw),
180
+ decode_deparment_id(raw),
181
+ decode_team_id(raw),
182
+ decode_product_id(raw),
183
+ decode_ticket_dates(raw),
184
+ )
185
+ second = smash.smash_result_5(
186
+ decode_ticket_properties(raw),
187
+ decode_contact_id(raw),
188
+ JsonUnfolder.require(raw, "email", DecodeUtils.to_str),
189
+ JsonUnfolder.require(raw, "phone", DecodeUtils.to_str),
190
+ decode_maybe_str(raw, "onholdTime"),
191
+ )
192
+ return smash.smash_result_2(first, second).map(lambda v: TicketObj(*v[0], *v[1]))
@@ -0,0 +1,117 @@
1
+ from dataclasses import dataclass
2
+
3
+ from fa_purity import Maybe
4
+ from fa_purity.date_time import DatetimeUTC
5
+
6
+ from fluidattacks_zoho_sdk.ids import (
7
+ AccountId,
8
+ ContactId,
9
+ CrmId,
10
+ DeparmentId,
11
+ ProductId,
12
+ ProfileId,
13
+ RoleId,
14
+ TeamId,
15
+ TicketId,
16
+ UserId,
17
+ )
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class UserObj:
22
+ id_user: UserId
23
+ first_name: str
24
+ last_name: str
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class AgentObj:
29
+ user: UserObj
30
+ is_confirmed: bool
31
+ email: str
32
+ profile: ProfileId
33
+ role: RoleId
34
+ status: str
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ContactDates:
39
+ created_time: DatetimeUTC
40
+ modified_time: DatetimeUTC
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class ContactInfo:
45
+ email: str
46
+ facebook: Maybe[str]
47
+ phone: str
48
+ mobile: Maybe[str]
49
+ secondary_email: Maybe[str]
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class ContactAddres:
54
+ city: Maybe[str]
55
+ country: Maybe[str]
56
+ street: Maybe[str]
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class ContactObj:
61
+ user: UserObj
62
+ contact_info: ContactInfo
63
+ contact_addres: ContactAddres
64
+ contact_dates: ContactDates
65
+ account_id: AccountId
66
+ contact_owner: UserId
67
+ crm_id: CrmId
68
+ description: Maybe[str]
69
+ state: Maybe[str]
70
+ title: Maybe[str]
71
+ type_contact: Maybe[str]
72
+ zip_contact: Maybe[str]
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class TeamObj:
77
+ id_deparment: DeparmentId
78
+ id_team: TeamId
79
+ description: str
80
+ name: str
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class TicketDates:
85
+ modified_time: Maybe[DatetimeUTC]
86
+ created_time: DatetimeUTC
87
+ closed_time: Maybe[DatetimeUTC]
88
+ customer_response_time: Maybe[DatetimeUTC]
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class TicketProperties:
93
+ id_ticket: TicketId
94
+ ticket_number: str
95
+ suject: Maybe[str]
96
+ channel: Maybe[str]
97
+ status: Maybe[str]
98
+ category: Maybe[str]
99
+ is_escalated: bool
100
+ priority: str
101
+ resolution: Maybe[str]
102
+ classification: Maybe[str]
103
+ description: Maybe[str]
104
+
105
+
106
+ @dataclass(frozen=True)
107
+ class TicketObj:
108
+ account_id: AccountId
109
+ deparment_id: DeparmentId
110
+ team_id: TeamId
111
+ product_id: ProductId
112
+ ticket_date: TicketDates
113
+ ticket_properties: TicketProperties
114
+ contact_id: ContactId
115
+ email: str
116
+ phone: str
117
+ onhold_time: Maybe[str]
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluidattacks_zoho_sdk
3
+ Version: 1.0.0
4
+ Summary: zoho SDK
5
+ Author-email: Product Team <development@fluidattacks.com>
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: fa-purity >=2.5.0, <3.0.0
8
+ Requires-Dist: fluidattacks-etl-utils >=1.0.0, <2.0.0
9
+ Requires-Dist: pure-requests >=3.0.0, <4.0.0
@@ -0,0 +1,16 @@
1
+ fluidattacks_zoho_sdk/__init__.py,sha256=gJQK6zYoY-Lb4Z7_DeCB5U1U7YvfKqoGLqnpLTK65cA,146
2
+ fluidattacks_zoho_sdk/_decoders.py,sha256=-el9TJI9n6pE2bmwuYK22V9iZa14QyKclw6SkbPpB_g,5272
3
+ fluidattacks_zoho_sdk/ids.py,sha256=mWqttCK4__D5TWayl_vf7q_H8zHSxSCcYuAIqD6K2sA,837
4
+ fluidattacks_zoho_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ fluidattacks_zoho_sdk/auth/__init__.py,sha256=jHqC0muxwU-loTAtd4n94tyoBUfRjXuLHKyePWxZjzE,901
6
+ fluidattacks_zoho_sdk/auth/_access.py,sha256=JTF8qUvTMvkwDOAn_m-vIyssteEzOM_INR_sxWoQ_24,2229
7
+ fluidattacks_zoho_sdk/auth/_core.py,sha256=-r-yYX6M5ocNjqnYJkZEO8gLLiyPg-bWGU4wrdCVl7I,738
8
+ fluidattacks_zoho_sdk/auth/_decode.py,sha256=oYxxSABGOrVGUHiWGhGTmKMqHTCiw_PR-EVymAKly3Q,1552
9
+ fluidattacks_zoho_sdk/auth/_refresh.py,sha256=Tgfy7K4SbuGW4uFB-kWNJAa8pdz4rjs0vV-WKSP6sRg,3031
10
+ fluidattacks_zoho_sdk/auth/_revoke.py,sha256=Urv3fY0kOSAKArImz-C5EDIe44cm_L5ZNjkYxIWqL2E,2412
11
+ fluidattacks_zoho_sdk/zoho_desk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ fluidattacks_zoho_sdk/zoho_desk/_decode.py,sha256=E7nthNHKHtsVgnPtnhU1xahCs2Aza6ajPO2jjUNhkTU,6387
13
+ fluidattacks_zoho_sdk/zoho_desk/core.py,sha256=3ItHmjEGhl86G0sF1X440KbwWtIKB0-dGIiSjVxfrcA,2215
14
+ fluidattacks_zoho_sdk-1.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
15
+ fluidattacks_zoho_sdk-1.0.0.dist-info/METADATA,sha256=wGlydBuRr-CcAsCF0W3QagY3qrmK02tLP-7SLdMVEDM,305
16
+ fluidattacks_zoho_sdk-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any