fluidattacks_zoho_sdk 1.0.0__py3-none-any.whl → 2.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,151 @@
1
+ from __future__ import (
2
+ annotations,
3
+ )
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import (
7
+ dataclass,
8
+ field,
9
+ )
10
+
11
+ from fa_purity import Cmd, Coproduct, FrozenList, Result, ResultE, UnitType, unit
12
+ from fa_purity.json import (
13
+ JsonObj,
14
+ )
15
+ from pure_requests.retry import (
16
+ MaxRetriesReached,
17
+ )
18
+ from requests import Response
19
+ from requests.exceptions import (
20
+ ChunkedEncodingError as RawChunkedEncodingError,
21
+ )
22
+ from requests.exceptions import (
23
+ ConnectionError as RawConnectionError,
24
+ )
25
+ from requests.exceptions import (
26
+ HTTPError as RawHTTPError,
27
+ )
28
+ from requests.exceptions import (
29
+ JSONDecodeError as RawJSONDecodeError,
30
+ )
31
+ from requests.exceptions import (
32
+ RequestException as RawRequestException,
33
+ )
34
+
35
+ from fluidattacks_zoho_sdk.auth import Token
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class UnhandledErrors:
40
+ error: Coproduct[JSONDecodeError, Coproduct[HTTPError, RequestException]]
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class HandledErrors:
45
+ error: Coproduct[
46
+ HTTPError,
47
+ Coproduct[ChunkedEncodingError, RequestsConnectionError],
48
+ ]
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class _Private:
53
+ pass
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class HTTPError:
58
+ raw: RawHTTPError
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class JSONDecodeError:
63
+ raw: RawJSONDecodeError
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class ChunkedEncodingError:
68
+ _private: _Private = field(repr=False, hash=False, compare=False)
69
+ raw: RawChunkedEncodingError
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class RequestsConnectionError:
74
+ _private: _Private = field(repr=False, hash=False, compare=False)
75
+ raw: RawConnectionError
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class RequestException:
80
+ raw: RawRequestException
81
+
82
+ def to_chunk_error(self) -> ResultE[ChunkedEncodingError]:
83
+ if isinstance(self.raw, RawChunkedEncodingError):
84
+ return Result.success(ChunkedEncodingError(_Private(), self.raw))
85
+ return Result.failure(ValueError("Not a ChunkedEncodingError"))
86
+
87
+ def to_connection_error(self) -> ResultE[RequestsConnectionError]:
88
+ if isinstance(self.raw, RawConnectionError):
89
+ return Result.success(RequestsConnectionError(_Private(), self.raw))
90
+ return Result.failure(ValueError("Not a RequestsConnectionError"))
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class RelativeEndpoint:
95
+ paths: FrozenList[str]
96
+
97
+ @staticmethod
98
+ def new(*args: str) -> RelativeEndpoint:
99
+ return RelativeEndpoint(tuple(args))
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class HttpJsonClient:
104
+ get: Callable[
105
+ [RelativeEndpoint, JsonObj], # Token
106
+ Cmd[
107
+ Result[
108
+ Coproduct[JsonObj, FrozenList[JsonObj]],
109
+ Coproduct[UnhandledErrors, MaxRetriesReached],
110
+ ]
111
+ ],
112
+ ]
113
+ post: Callable[
114
+ [RelativeEndpoint, JsonObj],
115
+ Cmd[
116
+ Result[
117
+ Coproduct[JsonObj, FrozenList[JsonObj]],
118
+ Coproduct[UnhandledErrors, MaxRetriesReached],
119
+ ]
120
+ ],
121
+ ]
122
+
123
+ get_response: Callable[
124
+ [RelativeEndpoint, JsonObj],
125
+ Cmd[Result[Response, Coproduct[UnhandledErrors, MaxRetriesReached]]],
126
+ ]
127
+
128
+
129
+ @dataclass(frozen=True)
130
+ class TokenManager:
131
+ @dataclass(frozen=True)
132
+ class _Private:
133
+ pass
134
+
135
+ _private: TokenManager._Private = field(repr=False, hash=False, compare=False)
136
+ _inner: dict[UnitType, Token]
137
+
138
+ @staticmethod
139
+ def new(token: Token) -> Cmd[TokenManager]:
140
+ return Cmd.wrap_impure(lambda: TokenManager(TokenManager._Private(), {unit: token}))
141
+
142
+ @property
143
+ def get(self) -> Cmd[Token]:
144
+ return Cmd.wrap_impure(lambda: self._inner[unit])
145
+
146
+ def update(self, token: Token) -> Cmd[UnitType]:
147
+ def _action() -> UnitType:
148
+ self._inner[unit] = token
149
+ return unit
150
+
151
+ return Cmd.wrap_impure(_action)
@@ -0,0 +1,35 @@
1
+ from collections.abc import Callable
2
+ from typing import NewType, TypeVar
3
+
4
+ from fa_purity import Cmd, FrozenList, Maybe, Result, ResultE
5
+ from fa_purity._core.utils import raise_exception
6
+
7
+ from ._core import HttpJsonClient
8
+
9
+ _T = TypeVar("_T")
10
+ FromIndex = NewType("FromIndex", int)
11
+ Limit = NewType("Limit", int)
12
+
13
+
14
+ def validate_next_page(
15
+ from_index: FromIndex,
16
+ items: FrozenList[_T],
17
+ limit: Limit,
18
+ ) -> ResultE[tuple[FrozenList[_T], Maybe[FromIndex]]]:
19
+ return Result.success(
20
+ (items, Maybe.some(FromIndex(from_index + limit)) if len(items) > 0 else Maybe.empty()),
21
+ )
22
+
23
+
24
+ def get_page(
25
+ client: HttpJsonClient,
26
+ from_index: Maybe[FromIndex],
27
+ limit: Limit,
28
+ get_endpoint: Callable[
29
+ [HttpJsonClient, Maybe[FromIndex], Limit],
30
+ Cmd[ResultE[tuple[FrozenList[_T], Maybe[FromIndex]]]],
31
+ ],
32
+ ) -> Cmd[tuple[FrozenList[_T], Maybe[FromIndex]]]:
33
+ return get_endpoint(client, from_index, limit).map(
34
+ lambda r: r.alt(raise_exception).to_union(),
35
+ )
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from fluidattacks_zoho_sdk._http_client import ClientFactory, HttpJsonClient, TokenManager
6
+ from fluidattacks_zoho_sdk.auth import Credentials
7
+ from fluidattacks_zoho_sdk.ids import OrgId
8
+
9
+ from . import _client
10
+ from .core import BulkClient, BulkEndpoint, FileName, ModuleName
11
+
12
+
13
+ def _from_client(client: HttpJsonClient) -> BulkClient:
14
+ return BulkClient(
15
+ lambda m, i: _client.create_bulk_export(client, m, i),
16
+ lambda obj: _client.get_status_bulk_export(client, obj),
17
+ lambda b, f: _client.download_bulk(client, b, f),
18
+ lambda m, e, f: _client.fetch_bulk(client, m, e, f),
19
+ )
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class BulkApiFactory:
24
+ @staticmethod
25
+ def new(creds: Credentials, org_id: OrgId, token: TokenManager) -> BulkClient:
26
+ return _from_client(ClientFactory.new(creds, org_id, token))
27
+
28
+
29
+ __all__ = ["BulkApiFactory", "BulkClient", "BulkEndpoint", "FileName", "ModuleName"]
@@ -0,0 +1,195 @@
1
+ import inspect
2
+ import logging
3
+
4
+ from fa_purity import Cmd, FrozenList, Result, ResultE, Unsafe, cast_exception
5
+ from fa_purity.json import JsonObj, Primitive, UnfoldedFactory
6
+ from fluidattacks_etl_utils.bug import Bug
7
+ from fluidattacks_etl_utils.retry import retry_cmd
8
+ from requests import Response
9
+
10
+ from fluidattacks_zoho_sdk._decoders import assert_single
11
+ from fluidattacks_zoho_sdk._http_client import HttpJsonClient, RelativeEndpoint
12
+
13
+ from ._decode import decode_bulk_info, transform_columns
14
+ from .core import (
15
+ BulkData,
16
+ BulkEndpoint,
17
+ BulkExportObj,
18
+ BulkStatus,
19
+ FileName,
20
+ ModuleName,
21
+ ShouldRetry,
22
+ )
23
+ from .utils import _handle_status, _wait_job, unzip_data
24
+
25
+ LOG = logging.getLogger(__name__)
26
+
27
+
28
+ def _retry_waiting_step(
29
+ retry: int,
30
+ result: ResultE[BulkStatus],
31
+ job: BulkExportObj,
32
+ client: HttpJsonClient,
33
+ ) -> Cmd[ResultE[BulkStatus]]:
34
+ error_or_ok = result.to_coproduct().map(lambda _s: False, lambda e: isinstance(e, ShouldRetry))
35
+ if error_or_ok is True:
36
+ return _wait_job(retry, job.id_bulk).bind(
37
+ lambda _: get_status_bulk_export(client, job).map( # token
38
+ lambda r: r.bind(lambda tup: _handle_status(tup[1])),
39
+ ),
40
+ )
41
+
42
+ return Cmd.wrap_value(result)
43
+
44
+
45
+ def create_bulk_export(
46
+ client: HttpJsonClient,
47
+ # access_token: Token,
48
+ module: ModuleName,
49
+ endpoint_bulk: BulkEndpoint,
50
+ ) -> Cmd[ResultE[tuple[BulkExportObj, BulkStatus]]]:
51
+ endpoint = RelativeEndpoint.new(endpoint_bulk.route)
52
+ params: dict[str, Primitive] = {"module": module.module}
53
+
54
+ return client.post(endpoint, UnfoldedFactory.from_dict(params)).map(
55
+ lambda result: (
56
+ result.alt(
57
+ lambda e: cast_exception(
58
+ Bug.new("_create_bulk_export", inspect.currentframe(), e, ()),
59
+ ),
60
+ )
61
+ .bind(assert_single)
62
+ .bind(decode_bulk_info)
63
+ ),
64
+ )
65
+
66
+
67
+ def get_status_retry(
68
+ client: HttpJsonClient,
69
+ bulk: BulkExportObj,
70
+ max_attempts: int,
71
+ ) -> Cmd[ResultE[BulkStatus]]:
72
+ def get_attempt(
73
+ obj: BulkExportObj,
74
+ client: HttpJsonClient,
75
+ ) -> Cmd[ResultE[BulkStatus]]:
76
+ return get_status_bulk_export(client, obj).map(
77
+ lambda result: (
78
+ result.alt(
79
+ lambda e: cast_exception(
80
+ Bug.new("_get_bulk_export_status", inspect.currentframe(), e, ()),
81
+ ),
82
+ ).bind(lambda tup: _handle_status(tup[1]))
83
+ ),
84
+ )
85
+
86
+ log = Cmd.wrap_impure(lambda: LOG.info("[API]: Starting read status of bulk export"))
87
+ handled = log + get_attempt(bulk, client) # token
88
+
89
+ return retry_cmd(
90
+ handled,
91
+ lambda retry, result: _retry_waiting_step(retry, result, bulk, client), # token
92
+ max_attempts,
93
+ ).map(lambda r: r.alt(cast_exception))
94
+
95
+
96
+ def get_status_bulk_export(
97
+ client: HttpJsonClient,
98
+ # access_token: Token,
99
+ bulk: BulkExportObj,
100
+ ) -> Cmd[ResultE[tuple[BulkExportObj, BulkStatus]]]:
101
+ endpoint = RelativeEndpoint.new("bulkExport", bulk.id_bulk.id_bulk)
102
+ params: dict[str, Primitive] = {}
103
+
104
+ return client.get(endpoint, UnfoldedFactory.from_dict(params)).map( # access_token
105
+ lambda result: (
106
+ result.alt(
107
+ lambda e: cast_exception(
108
+ Bug.new("_get_bulk_export_status", inspect.currentframe(), e, ()),
109
+ ),
110
+ )
111
+ .bind(assert_single)
112
+ .bind(decode_bulk_info)
113
+ ),
114
+ )
115
+
116
+
117
+ def _unwrap_and_unzip(
118
+ result: Result[Response, Exception],
119
+ file_name: FileName,
120
+ ) -> Cmd[ResultE[BulkData]]:
121
+ return result.to_coproduct().map(
122
+ lambda inl: unzip_data(inl, file_name),
123
+ lambda ri: Cmd.wrap_value(Result.failure(ri)),
124
+ )
125
+
126
+
127
+ def download_bulk(
128
+ client: HttpJsonClient,
129
+ # access_token: Token,
130
+ bulk: BulkExportObj,
131
+ file_name: FileName,
132
+ ) -> Cmd[ResultE[BulkData]]:
133
+ endpoint = RelativeEndpoint.new("downloadBulkExportFile")
134
+ params: dict[str, Primitive] = {"exportId": bulk.id_bulk.id_bulk}
135
+
136
+ return (
137
+ client.get_response(endpoint, UnfoldedFactory.from_dict(params))
138
+ .map(
139
+ lambda result: result.alt(
140
+ lambda e: cast_exception(
141
+ Bug.new("_download_bulk_export", inspect.currentframe(), e, ()),
142
+ ),
143
+ ),
144
+ )
145
+ .bind(lambda v: _unwrap_and_unzip(v, file_name))
146
+ )
147
+
148
+
149
+ def _wait_and_download(
150
+ bulk_obj: BulkExportObj,
151
+ client: HttpJsonClient,
152
+ file_name: FileName,
153
+ ) -> Cmd[ResultE[BulkData]]:
154
+ return (
155
+ get_status_retry(client, bulk_obj, 10) # token
156
+ .map(lambda r: r.map(lambda _: bulk_obj))
157
+ .map(lambda r: r.alt(Unsafe.raise_exception).to_union())
158
+ .bind(lambda bulk: download_bulk(client, bulk, file_name))
159
+ )
160
+
161
+
162
+ def transform_csv_phase(csv: ResultE[BulkData]) -> ResultE[FrozenList[JsonObj]]:
163
+ bulk_data = csv.alt(Unsafe.raise_exception).to_union()
164
+ return transform_columns(bulk_data)
165
+
166
+
167
+ def fetch_bulk(
168
+ client: HttpJsonClient,
169
+ # access_token: Token,
170
+ module: ModuleName,
171
+ endpoint_bulk: BulkEndpoint,
172
+ file_name: FileName,
173
+ ) -> Cmd[ResultE[FrozenList[JsonObj]]]:
174
+ create_bulk: Cmd[ResultE[tuple[BulkExportObj, BulkStatus]]] = create_bulk_export(
175
+ client,
176
+ # access_token,
177
+ module,
178
+ endpoint_bulk,
179
+ )
180
+
181
+ def on_created(
182
+ result: ResultE[tuple[BulkExportObj, BulkStatus]],
183
+ ) -> Cmd[ResultE[FrozenList[JsonObj]]]:
184
+ return (
185
+ result.map(lambda pair: pair[0])
186
+ .to_coproduct()
187
+ .map(
188
+ lambda bulk_obj: _wait_and_download(bulk_obj, client, file_name).map(
189
+ lambda data: transform_csv_phase(data),
190
+ ),
191
+ lambda err: Cmd.wrap_value(Result.failure(err)),
192
+ )
193
+ )
194
+
195
+ return create_bulk.bind(on_created)
@@ -0,0 +1,41 @@
1
+ import csv
2
+
3
+ from fa_purity import FrozenList, ResultE
4
+ from fa_purity.json import JsonObj, JsonUnfolder, JsonValueFactory, Unfolder
5
+ from fluidattacks_etl_utils import smash
6
+ from fluidattacks_etl_utils.decode import DecodeUtils
7
+
8
+ from .core import BulkData, BulkExportId, BulkExportObj, BulkStatus, ViewId
9
+
10
+
11
+ def decode_id_bulk(raw: JsonObj) -> ResultE[BulkExportId]:
12
+ return JsonUnfolder.require(raw, "exportId", DecodeUtils.to_str).map(lambda v: BulkExportId(v))
13
+
14
+
15
+ def decode_id_view(raw: JsonObj) -> ResultE[ViewId]:
16
+ return JsonUnfolder.require(raw, "viewId", DecodeUtils.to_str).map(lambda v: ViewId(v))
17
+
18
+
19
+ def decode_status_bulk(raw: JsonObj) -> ResultE[BulkStatus]:
20
+ return JsonUnfolder.require(raw, "status", DecodeUtils.to_str).bind(
21
+ lambda v: BulkStatus.from_raw(v),
22
+ )
23
+
24
+
25
+ def decode_bulk_export(raw: JsonObj) -> ResultE[BulkExportObj]:
26
+ return smash.smash_result_2(
27
+ decode_id_bulk(raw),
28
+ JsonUnfolder.require(raw, "module", DecodeUtils.to_str),
29
+ ).map(lambda bulk: BulkExportObj(*bulk))
30
+
31
+
32
+ def decode_bulk_info(raw: JsonObj) -> ResultE[tuple[BulkExportObj, BulkStatus]]:
33
+ return smash.smash_result_2(decode_bulk_export(raw), decode_status_bulk(raw))
34
+
35
+
36
+ def transform_columns(bulk: BulkData) -> ResultE[FrozenList[JsonObj]]:
37
+ bulk.file.seek(0)
38
+ reader = csv.DictReader(bulk.file)
39
+ rows: list[dict[str, str | None]] = list(reader)
40
+
41
+ return JsonValueFactory.from_any(rows).bind(lambda v: Unfolder.to_list_of(v, Unfolder.to_json))
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import IO, Final, final
7
+
8
+ from fa_purity import Cmd, FrozenList, ResultE
9
+ from fa_purity.json import JsonObj
10
+ from fluidattacks_etl_utils.handle_errors import handle_value_error
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class BulkExportId:
15
+ id_bulk: str
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ViewId:
20
+ id_bulk: str
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class ModuleName:
25
+ module: str
26
+
27
+
28
+ @final
29
+ class BulkStatus(Enum):
30
+ QUEUED = "QUEUED"
31
+ INITIATED = "INITIATED"
32
+ IN_PROGRESS = "IN_PROGRESS"
33
+ COMPLETED = "COMPLETED"
34
+ FAILED = "FAILED"
35
+ EXPIRED = "EXPIRED"
36
+ CANCELLED = "CANCELLED"
37
+
38
+ @staticmethod
39
+ def from_raw(raw: str) -> ResultE[BulkStatus]:
40
+ return handle_value_error(lambda: BulkStatus(raw))
41
+
42
+
43
+ NonTerminalStatus: Final[tuple[BulkStatus, ...]] = (
44
+ BulkStatus.QUEUED,
45
+ BulkStatus.IN_PROGRESS,
46
+ BulkStatus.INITIATED,
47
+ )
48
+ TerminalStatus: Final[tuple[BulkStatus, ...]] = (
49
+ BulkStatus.CANCELLED,
50
+ BulkStatus.FAILED,
51
+ BulkStatus.EXPIRED,
52
+ )
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class BulkExportObj:
57
+ id_bulk: BulkExportId
58
+ module: str
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class BulkEndpoint:
63
+ route: str
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class ShouldRetry(Exception):
68
+ status: BulkStatus
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class MustRestart(Exception):
73
+ status: BulkStatus
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class BulkClient:
78
+ create_bulk: Callable[
79
+ [ModuleName, BulkEndpoint],
80
+ Cmd[ResultE[tuple[BulkExportObj, BulkStatus]]],
81
+ ]
82
+ get_status: Callable[[BulkExportObj], Cmd[ResultE[tuple[BulkExportObj, BulkStatus]]]]
83
+ download_bulk: Callable[[BulkExportObj, FileName], Cmd[ResultE[BulkData]]]
84
+ fetch_bulk: Callable[
85
+ [ModuleName, BulkEndpoint, FileName],
86
+ Cmd[ResultE[FrozenList[JsonObj]]],
87
+ ]
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class BulkData:
92
+ file: IO[str]
93
+
94
+
95
+ @dataclass(frozen=True)
96
+ class FileName:
97
+ name: str
@@ -0,0 +1,72 @@
1
+ import logging
2
+ import tempfile
3
+ from typing import IO
4
+ from zipfile import ZipFile
5
+
6
+ from fa_purity import Cmd, Result, ResultE, cast_exception
7
+ from pure_requests.retry import sleep_cmd
8
+ from requests import Response
9
+
10
+ from .core import (
11
+ BulkData,
12
+ BulkExportId,
13
+ BulkStatus,
14
+ FileName,
15
+ MustRestart,
16
+ NonTerminalStatus,
17
+ ShouldRetry,
18
+ )
19
+
20
+ LOG = logging.getLogger(__name__)
21
+
22
+
23
+ def _top_truncation(value: float, limit: float) -> float:
24
+ if value > limit:
25
+ return limit
26
+ return value
27
+
28
+
29
+ def _waiting_msg(job: BulkExportId, time: float) -> Cmd[None]:
30
+ return Cmd.wrap_impure(
31
+ lambda: LOG.info("Waiting bulk export %s to be ready (%s)", job.id_bulk, int(time)),
32
+ )
33
+
34
+
35
+ def _wait_job(retry: int, job: BulkExportId) -> Cmd[None]:
36
+ wait_time = _top_truncation(60 * retry, 5 * 50)
37
+ return _waiting_msg(job, wait_time) + sleep_cmd(wait_time)
38
+
39
+
40
+ def _handle_status(status_bulk: BulkStatus) -> ResultE[BulkStatus]:
41
+ if status_bulk == BulkStatus.COMPLETED:
42
+ return Result.success(status_bulk)
43
+ if status_bulk in NonTerminalStatus:
44
+ return Result.failure(ShouldRetry(status_bulk))
45
+ return Result.failure(MustRestart(status_bulk))
46
+
47
+
48
+ def unzip_data(response: Response, file_name: FileName) -> Cmd[ResultE[BulkData]]:
49
+ def _action() -> ResultE[BulkData]:
50
+ # pylint: disable=consider-using-with
51
+ # need refac of BulkData for enabling the above check
52
+ name_file = file_name.name
53
+ tmp_zipdir = tempfile.mkdtemp()
54
+ file_zip: IO[bytes] = tempfile.NamedTemporaryFile(mode="wb+") # noqa: SIM115
55
+ file_unzip: IO[str] = tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") # noqa: SIM115
56
+ file_zip.write(response.content)
57
+ file_zip.seek(0)
58
+ LOG.debug("Unzipping file")
59
+ with ZipFile(file_zip, "r") as zip_obj:
60
+ files = zip_obj.namelist()
61
+
62
+ if name_file not in files:
63
+ err = ValueError(f"Expected {name_file} file. Decompressed {len(files)} files.")
64
+ return Result.failure(cast_exception(err))
65
+ zip_obj.extract(name_file, tmp_zipdir)
66
+ LOG.debug("Generating BulkData")
67
+ with open(tmp_zipdir + f"/{name_file}", encoding="UTF-8") as unzipped: # noqa: PTH123
68
+ file_unzip.write(unzipped.read())
69
+ LOG.debug("Unzipped size: %s", file_unzip.tell())
70
+ return Result.success(BulkData(file_unzip))
71
+
72
+ return Cmd.wrap_impure(_action)
@@ -1,9 +1,13 @@
1
1
  from dataclasses import dataclass
2
2
 
3
- from fa_purity import Maybe
4
3
  from fluidattacks_etl_utils.natural import Natural
5
4
 
6
5
 
6
+ @dataclass(frozen=True)
7
+ class OrgId:
8
+ org_id: Natural
9
+
10
+
7
11
  @dataclass(frozen=True)
8
12
  class UserId:
9
13
  user_id: Natural
@@ -11,7 +15,7 @@ class UserId:
11
15
 
12
16
  @dataclass(frozen=True)
13
17
  class AccountId:
14
- accound_id: Natural
18
+ account_id: Natural
15
19
 
16
20
 
17
21
  @dataclass(frozen=True)
@@ -21,7 +25,7 @@ class CrmId:
21
25
 
22
26
  @dataclass(frozen=True)
23
27
  class DeparmentId:
24
- id_deparment: Maybe[Natural]
28
+ id_deparment: Natural
25
29
 
26
30
 
27
31
  @dataclass(frozen=True)
@@ -46,7 +50,7 @@ class RoleId:
46
50
 
47
51
  @dataclass(frozen=True)
48
52
  class ProductId:
49
- id_product: Maybe[Natural]
53
+ id_product: Natural
50
54
 
51
55
 
52
56
  @dataclass(frozen=True)