fluidattacks_integrates_dal 2.3.0__tar.gz → 2.3.2__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.
Files changed (32) hide show
  1. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/PKG-INFO +2 -1
  2. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/build/default.nix +4 -8
  3. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/flake.lock +22 -60
  4. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/flake.nix +3 -4
  5. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/__init__.py +1 -1
  6. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/_cli.py +4 -4
  7. fluidattacks_integrates_dal-2.3.2/fluidattacks_integrates_dal/_typing.py +11 -0
  8. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/clients/group/__init__.py +2 -2
  9. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/clients/organization/__init__.py +27 -18
  10. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/clients/out_of_scope/__init__.py +31 -14
  11. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/core.py +9 -7
  12. fluidattacks_integrates_dal-2.3.2/fluidattacks_integrates_dal/utils.py +38 -0
  13. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/pyproject.toml +3 -2
  14. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/arch/arch.py +3 -8
  15. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/arch/test_arch.py +2 -2
  16. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/test_list_groups.py +6 -2
  17. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/uv.lock +306 -265
  18. fluidattacks_integrates_dal-2.3.0/fluidattacks_integrates_dal/_typing.py +0 -17
  19. fluidattacks_integrates_dal-2.3.0/fluidattacks_integrates_dal/utils.py +0 -32
  20. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/.envrc +0 -0
  21. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/build/filter.nix +0 -0
  22. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/_logger.py +0 -0
  23. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/client.py +0 -0
  24. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/clients/__init__.py +0 -0
  25. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/list_groups.py +0 -0
  26. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/fluidattacks_integrates_dal/py.typed +0 -0
  27. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/mypy.ini +0 -0
  28. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/ruff.toml +0 -0
  29. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/__init__.py +0 -0
  30. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/arch/__init__.py +0 -0
  31. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/py.typed +0 -0
  32. {fluidattacks_integrates_dal-2.3.0 → fluidattacks_integrates_dal-2.3.2}/tests/test_core.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluidattacks_integrates_dal
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: ASM Synchronous Data Access Layer
5
5
  Author-email: Engineering Team <development@fluidattacks.com>
6
6
  Requires-Python: >=3.11
@@ -10,3 +10,4 @@ Requires-Dist: click >=8.1.7, <9.0.0
10
10
  Requires-Dist: fa-purity >=2.5.2, <3.0.0
11
11
  Requires-Dist: fluidattacks-etl-utils >=5.1.0, <6.0.0
12
12
  Requires-Dist: fluidattacks-utils-logger >=3.0.0, <4.0.0
13
+ Requires-Dist: mypy-boto3-dynamodb >=1.40.40, <2.0.0
@@ -1,13 +1,9 @@
1
- {
2
- nixpkgs,
3
- builders,
4
- scripts,
5
- src,
6
- }:
1
+ { src }:
7
2
  {
8
3
  inherit src;
9
4
  root_path = "observes/common/integrates-dal";
10
5
  module_name = "fluidattacks_integrates_dal";
11
- pypi_token_var = "INTEGRATES_DAL_TOKEN";
12
- override = b: b;
6
+ pypi_token = {
7
+ method = "oidc";
8
+ };
13
9
  }
@@ -98,11 +98,11 @@
98
98
  },
99
99
  "nixpkgs_flake": {
100
100
  "locked": {
101
- "lastModified": 1762812764,
102
- "narHash": "sha256-MNqrovkO8AEQeYQK7e3X2GDx2H8vfDIxV4K/VEaIjjo=",
101
+ "lastModified": 1778253243,
102
+ "narHash": "sha256-KkH2QvU6iRLDTGGEwH5jnMqHWj4zMTuHxi1S1zdr01w=",
103
103
  "owner": "nixos",
104
104
  "repo": "nixpkgs",
105
- "rev": "400156fe527ab6b54cf6f70cca2c8385548a29c4",
105
+ "rev": "4fcda7103530b23c5ca44286344912082aad4220",
106
106
  "type": "github"
107
107
  },
108
108
  "original": {
@@ -111,49 +111,6 @@
111
111
  "type": "github"
112
112
  }
113
113
  },
114
- "nixpkgs_flake_2": {
115
- "locked": {
116
- "lastModified": 1761668286,
117
- "narHash": "sha256-YN/cm/hbm7pi7lMZCsNhu6jlBUi4wUirrIlyOMpbjKU=",
118
- "owner": "nixos",
119
- "repo": "nixpkgs",
120
- "rev": "1fdb9eb10eff581a40ce2e9511c4ed517eac0c3f",
121
- "type": "github"
122
- },
123
- "original": {
124
- "owner": "nixos",
125
- "repo": "nixpkgs",
126
- "type": "github"
127
- }
128
- },
129
- "observes_flake_builder": {
130
- "inputs": {
131
- "nix_filter": "nix_filter",
132
- "nixpkgs_flake": "nixpkgs_flake",
133
- "pyproject-build-systems": "pyproject-build-systems",
134
- "pyproject-nix": "pyproject-nix_2",
135
- "pythonix_flake": "pythonix_flake",
136
- "shell-helpers": "shell-helpers",
137
- "uv2nix": "uv2nix_2"
138
- },
139
- "locked": {
140
- "dir": "observes/common/std_flake_2",
141
- "lastModified": 1770672786,
142
- "narHash": "sha256-W4qMpuS/Zg9P/PooFDsqW7EzjF3A+ITsxKbp1SWdARI=",
143
- "ref": "refs/heads/trunk",
144
- "rev": "ad927cc6545df64a7d622696a43bcee225b9867b",
145
- "shallow": true,
146
- "type": "git",
147
- "url": "ssh://git@gitlab.com/fluidattacks/universe"
148
- },
149
- "original": {
150
- "dir": "observes/common/std_flake_2",
151
- "rev": "ad927cc6545df64a7d622696a43bcee225b9867b",
152
- "shallow": true,
153
- "type": "git",
154
- "url": "ssh://git@gitlab.com/fluidattacks/universe"
155
- }
156
- },
157
114
  "pyproject-build-systems": {
158
115
  "inputs": {
159
116
  "nixpkgs": "nixpkgs",
@@ -177,7 +134,7 @@
177
134
  "pyproject-nix": {
178
135
  "inputs": {
179
136
  "nixpkgs": [
180
- "observes_flake_builder",
137
+ "python_builder",
181
138
  "pyproject-build-systems",
182
139
  "nixpkgs"
183
140
  ]
@@ -217,7 +174,7 @@
217
174
  "pyproject-nix_3": {
218
175
  "inputs": {
219
176
  "nixpkgs": [
220
- "observes_flake_builder",
177
+ "python_builder",
221
178
  "uv2nix",
222
179
  "nixpkgs"
223
180
  ]
@@ -236,23 +193,28 @@
236
193
  "type": "github"
237
194
  }
238
195
  },
239
- "pythonix_flake": {
196
+ "python_builder": {
240
197
  "inputs": {
241
- "nixpkgs_flake": "nixpkgs_flake_2"
198
+ "nix_filter": "nix_filter",
199
+ "nixpkgs_flake": "nixpkgs_flake",
200
+ "pyproject-build-systems": "pyproject-build-systems",
201
+ "pyproject-nix": "pyproject-nix_2",
202
+ "shell-helpers": "shell-helpers",
203
+ "uv2nix": "uv2nix_2"
242
204
  },
243
205
  "locked": {
244
- "dir": "observes/common/pythonix_mirror",
245
- "lastModified": 1767370831,
246
- "narHash": "sha256-VR86TSrJsNHQq31Ksga+1dvsrkv9u1pKa0MKuKYeIAc=",
206
+ "dir": "common/utils/python-builder",
207
+ "lastModified": 1785790367,
208
+ "narHash": "sha256-Br5oTB6LNGANZMUecvZhlXpA0k0wb0EKmOqyQeYCe7A=",
247
209
  "ref": "refs/heads/trunk",
248
- "rev": "0678f8ff3bfe02cfe23bc819bfa739c53bd6e4b6",
210
+ "rev": "f66f38301987775ceb39ee72388ef0885ef30d13",
249
211
  "shallow": true,
250
212
  "type": "git",
251
213
  "url": "ssh://git@gitlab.com/fluidattacks/universe"
252
214
  },
253
215
  "original": {
254
- "dir": "observes/common/pythonix_mirror",
255
- "rev": "0678f8ff3bfe02cfe23bc819bfa739c53bd6e4b6",
216
+ "dir": "common/utils/python-builder",
217
+ "rev": "f66f38301987775ceb39ee72388ef0885ef30d13",
256
218
  "shallow": true,
257
219
  "type": "git",
258
220
  "url": "ssh://git@gitlab.com/fluidattacks/universe"
@@ -260,14 +222,14 @@
260
222
  },
261
223
  "root": {
262
224
  "inputs": {
263
- "observes_flake_builder": "observes_flake_builder"
225
+ "python_builder": "python_builder"
264
226
  }
265
227
  },
266
228
  "shell-helpers": {
267
229
  "inputs": {
268
230
  "flake-parts": "flake-parts",
269
231
  "nixpkgs": [
270
- "observes_flake_builder",
232
+ "python_builder",
271
233
  "nixpkgs_flake"
272
234
  ]
273
235
  },
@@ -292,12 +254,12 @@
292
254
  "uv2nix": {
293
255
  "inputs": {
294
256
  "nixpkgs": [
295
- "observes_flake_builder",
257
+ "python_builder",
296
258
  "pyproject-build-systems",
297
259
  "nixpkgs"
298
260
  ],
299
261
  "pyproject-nix": [
300
- "observes_flake_builder",
262
+ "python_builder",
301
263
  "pyproject-build-systems",
302
264
  "pyproject-nix"
303
265
  ]
@@ -2,8 +2,8 @@
2
2
  description = "Common Integrates-dal";
3
3
 
4
4
  inputs = {
5
- observes_flake_builder = {
6
- url = "git+ssh://git@gitlab.com/fluidattacks/universe?shallow=1&rev=ad927cc6545df64a7d622696a43bcee225b9867b&dir=observes/common/std_flake_2";
5
+ python_builder = {
6
+ url = "git+ssh://git@gitlab.com/fluidattacks/universe?shallow=1&rev=f66f38301987775ceb39ee72388ef0885ef30d13&dir=common/utils/python-builder";
7
7
  };
8
8
  };
9
9
 
@@ -19,11 +19,10 @@
19
19
  scripts,
20
20
  }:
21
21
  import ./build {
22
- inherit nixpkgs builders scripts;
23
22
  src = import ./build/filter.nix nixpkgs.nix-filter self;
24
23
  };
25
24
  in
26
25
  {
27
- packages = inputs.observes_flake_builder.outputs.build build_args;
26
+ packages = inputs.python_builder.outputs.build build_args;
28
27
  };
29
28
  }
@@ -6,6 +6,6 @@ from ._logger import (
6
6
  set_logger,
7
7
  )
8
8
 
9
- __version__ = "2.3.0"
9
+ __version__ = "2.3.2"
10
10
 
11
11
  Unsafe.compute(set_logger(__name__, __version__))
@@ -42,10 +42,10 @@ def list_all_groups() -> NoReturn:
42
42
  list_groups.list_all_groups(t[0], t[1])
43
43
  .map(lambda gs: "\n".join(g.name for g in gs))
44
44
  .bind(
45
- lambda s: Cmd.wrap_impure(
46
- lambda: sys.stdout.write(s + "\n"), # type: ignore[misc]
47
- ).map(
48
- lambda _: None, # type: ignore[misc]
45
+ lambda s: (
46
+ Cmd[int]
47
+ .wrap_impure(lambda: sys.stdout.write(s + "\n")) # type: ignore[misc]
48
+ .map(lambda _: None)
49
49
  ),
50
50
  )
51
51
  ),
@@ -0,0 +1,11 @@
1
+ from collections.abc import Callable, Iterable, Iterator
2
+ from typing import Generic, NoReturn, TypeVar
3
+
4
+ __all__ = [
5
+ "Callable",
6
+ "Generic",
7
+ "Iterable",
8
+ "Iterator",
9
+ "NoReturn",
10
+ "TypeVar",
11
+ ]
@@ -125,7 +125,7 @@ def _get_groups_page(
125
125
  LOG.debug(page)
126
126
  return page
127
127
 
128
- return Cmd.wrap_impure(_action)
128
+ return Cmd[_Page].wrap_impure(_action)
129
129
 
130
130
 
131
131
  def _get_groups(table: Table, org: OrganizationId) -> Stream[GroupId]:
@@ -137,7 +137,7 @@ def _get_groups(table: Table, org: OrganizationId) -> Stream[GroupId]:
137
137
  lambda p: (
138
138
  _get_groups_page(table, org, p.last_index).map(_union.inl)
139
139
  if p and p.last_index
140
- else Cmd.wrap_value(None).map(_union.inr)
140
+ else Cmd[None].wrap_value(None).map(_union.inr)
141
141
  ),
142
142
  ),
143
143
  init.map(_union.inl),
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from typing import TYPE_CHECKING
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ )
5
7
 
6
8
  from fa_purity import (
7
9
  Cmd,
@@ -20,9 +22,11 @@ from fa_purity.json import (
20
22
  JsonPrimitiveFactory,
21
23
  JsonPrimitiveUnfolder,
22
24
  )
25
+ from mypy_boto3_dynamodb.type_defs import (
26
+ QueryOutputTypeDef,
27
+ )
23
28
 
24
29
  from fluidattacks_integrates_dal._typing import (
25
- Dict,
26
30
  Iterable,
27
31
  TypeVar,
28
32
  )
@@ -37,37 +41,40 @@ if TYPE_CHECKING:
37
41
  from mypy_boto3_dynamodb import (
38
42
  DynamoDBClient,
39
43
  )
40
- from mypy_boto3_dynamodb.type_defs import (
41
- QueryOutputTypeDef,
42
- )
43
44
  LOG = logging.getLogger(__name__)
44
45
  _T = TypeVar("_T")
45
46
  _K = TypeVar("_K")
46
47
  _DB_TABLE = "integrates_vms"
47
48
 
48
49
 
49
- def _require_key(item: Dict[_K, _T], key: _K) -> ResultE[_T]:
50
+ def _require_key(item: dict[_K, _T], key: _K) -> ResultE[_T]:
50
51
  try:
51
- return Result.success(item[key])
52
+ return Result[_T, Exception].success(item[key])
52
53
  except KeyError as err:
53
- return Result.failure(err).alt(cast_exception)
54
+ return Result[_T, KeyError].failure(err).alt(cast_exception)
54
55
 
55
56
 
56
- def _decode_dynamo_str(item: _T) -> ResultE[JsonPrimitive]:
57
+ def _decode_dynamo_str(item: object) -> ResultE[JsonPrimitive]:
57
58
  if isinstance(item, dict):
58
- return _require_key(item, "S").bind( # type: ignore[misc]
59
- JsonPrimitiveFactory.from_any,
59
+ # isinstance narrows to dict[Unknown, Unknown]; the actual key/value
60
+ # types are only known by convention (DynamoDB's typed-attribute maps).
61
+ s_value: ResultE[str] = _require_key(
62
+ item, # pyright: ignore[reportUnknownArgumentType]
63
+ "S",
60
64
  )
61
- return Result.failure(TypeError(f"Expected `dict` instance; got `{type(item)}`")).alt(
62
- cast_exception,
65
+ return s_value.bind(JsonPrimitiveFactory.from_any)
66
+ return (
67
+ Result[JsonPrimitive, TypeError]
68
+ .failure(TypeError(f"Expected `dict` instance; got `{type(item)}`"))
69
+ .alt(cast_exception)
63
70
  )
64
71
 
65
72
 
66
- def _decode_dynamo_key_str(item: Dict[str, _T], key: str) -> ResultE[JsonPrimitive]:
73
+ def _decode_dynamo_key_str(item: dict[str, _T], key: str) -> ResultE[JsonPrimitive]:
67
74
  return _require_key(item, key).bind(_decode_dynamo_str)
68
75
 
69
76
 
70
- def _decode_org_id(raw: Dict[str, _T]) -> ResultE[OrganizationId]:
77
+ def _decode_org_id(raw: dict[str, _T]) -> ResultE[OrganizationId]:
71
78
  _pk = _decode_dynamo_key_str(raw, "pk").bind(JsonPrimitiveUnfolder.to_str)
72
79
  _sk = _decode_dynamo_key_str(raw, "sk").bind(JsonPrimitiveUnfolder.to_str)
73
80
  return _pk.bind(lambda pk: _sk.bind(lambda sk: OrganizationId.new(pk, sk)))
@@ -85,7 +92,7 @@ def _to_items(page: QueryOutputTypeDef) -> FrozenList[OrganizationId]:
85
92
  def _all_orgs(client: DynamoDBClient) -> Stream[OrganizationId]:
86
93
  def _new_iter() -> Iterable[Cmd[QueryOutputTypeDef]]:
87
94
  LOG.debug("Getting all orgs")
88
- exp_attrs_values: Dict[str, Dict[str, str]] = {
95
+ exp_attrs_values: dict[str, dict[str, str]] = {
89
96
  ":pk": {"S": "ORG#all"},
90
97
  ":sk": {"S": "ORG#"},
91
98
  }
@@ -100,10 +107,12 @@ def _all_orgs(client: DynamoDBClient) -> Stream[OrganizationId]:
100
107
  IndexName="gsi_2",
101
108
  )
102
109
  LOG.debug("all orgs retrieved!")
103
- return map(Cmd.wrap_value, response) # type: ignore[misc]
110
+ return (Cmd[QueryOutputTypeDef].wrap_value(v) for v in response) # type: ignore[misc]
104
111
 
105
112
  data = StreamFactory.from_commands( # type: ignore[misc]
106
- Unsafe.pure_iter_from_cmd(Cmd.wrap_impure(_new_iter)), # type: ignore[misc]
113
+ Unsafe.pure_iter_from_cmd(
114
+ Cmd[Iterable[Cmd[QueryOutputTypeDef]]].wrap_impure(_new_iter), # type: ignore[misc]
115
+ ),
107
116
  )
108
117
  return (
109
118
  data.map(_to_items) # type: ignore[misc]
@@ -19,8 +19,10 @@ from fa_purity import (
19
19
  Cmd,
20
20
  FrozenList,
21
21
  Maybe,
22
+ PureIterFactory,
23
+ PureIterTransform,
24
+ ResultE,
22
25
  Stream,
23
- Unsafe,
24
26
  )
25
27
  from fa_purity.json import (
26
28
  JsonPrimitiveFactory,
@@ -66,15 +68,29 @@ _Cursor = Mapping[
66
68
  ]
67
69
 
68
70
 
69
- def _attr_str(item: Mapping[str, object], key: str) -> str:
70
- return (
71
- JsonPrimitiveFactory.from_any(item.get(key))
72
- .bind(JsonPrimitiveUnfolder.to_str)
73
- .alt(Unsafe.raise_exception)
74
- .to_union()
71
+ def _decode_str(item: Mapping[str, object], key: str) -> ResultE[str]:
72
+ return JsonPrimitiveFactory.from_any(item.get(key)).bind(JsonPrimitiveUnfolder.to_str)
73
+
74
+
75
+ def _decode_root(item: Mapping[str, object]) -> ResultE[OutOfScopeRoot]:
76
+ return _decode_str(item, "nickname").bind(
77
+ lambda nickname: _decode_str(item, "status").bind(
78
+ lambda status: OutOfScopeRoot.new(nickname, status),
79
+ ),
75
80
  )
76
81
 
77
82
 
83
+ def _warn_skip(error: Exception) -> Maybe[OutOfScopeRoot]:
84
+ LOG.warning("Skipping malformed out-of-scope root: %s", error)
85
+ return Maybe[OutOfScopeRoot].empty()
86
+
87
+
88
+ def _ignore_errors(result: ResultE[OutOfScopeRoot]) -> Maybe[OutOfScopeRoot]:
89
+ # A malformed cloning record (e.g. still in progress with no status) must not
90
+ # abort the enumeration: log it and drop it. The log is a negligible impurity.
91
+ return result.map(Maybe[OutOfScopeRoot].some).alt(_warn_skip).to_union()
92
+
93
+
78
94
  def _get_page(
79
95
  table: Table,
80
96
  org: OrganizationId,
@@ -90,15 +106,16 @@ def _get_page(
90
106
  ExclusiveStartKey=start_key,
91
107
  ),
92
108
  ).or_else_call(lambda: table.query(KeyConditionExpression=condition))
93
- roots = tuple(
94
- OutOfScopeRoot.new(_attr_str(i, "nickname"), _attr_str(i, "status"))
95
- .alt(Unsafe.raise_exception)
96
- .to_union()
97
- for i in response["Items"]
109
+ roots = (
110
+ PureIterFactory.from_list(tuple(response["Items"]))
111
+ .map(_decode_root)
112
+ .map(_ignore_errors)
113
+ .transform(PureIterTransform.filter_maybe)
114
+ .to_list()
98
115
  )
99
- return (roots, Maybe.from_optional(response.get("LastEvaluatedKey")))
116
+ return (roots, Maybe[_Cursor].from_optional(response.get("LastEvaluatedKey")))
100
117
 
101
- return Cmd.wrap_impure(_action)
118
+ return Cmd[tuple[FrozenList[OutOfScopeRoot], Maybe[_Cursor]]].wrap_impure(_action)
102
119
 
103
120
 
104
121
  def _get_roots(table: Table, org: OrganizationId) -> Stream[FrozenList[OutOfScopeRoot]]:
@@ -31,8 +31,8 @@ class GroupId:
31
31
  pattern = r"^[\w\s-]+$"
32
32
  if not re.match(pattern, _name):
33
33
  err = ValueError(rf"Group name is not `^[\w- ]+$` i.e. {_name}")
34
- return Result.failure(err, GroupId).alt(cast_exception)
35
- return Result.success(GroupId(_Private(), _name), Exception)
34
+ return Result[GroupId, ValueError].failure(err).alt(cast_exception)
35
+ return Result[GroupId, Exception].success(GroupId(_Private(), _name))
36
36
 
37
37
 
38
38
  @dataclass(frozen=True)
@@ -47,13 +47,13 @@ class OrganizationId:
47
47
  _uuid = uuid.removeprefix("ORG#")
48
48
  if not _name.isalnum():
49
49
  err = ValueError(f"Org name is not alphanum i.e. {_name}")
50
- return Result.failure(err, OrganizationId).alt(cast_exception)
50
+ return Result[OrganizationId, ValueError].failure(err).alt(cast_exception)
51
51
  uuidv4 = "^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
52
52
  if re.match(uuidv4, _uuid, re.IGNORECASE) is None:
53
53
  err = ValueError(f"Org id is not an UUIDv4 i.e. {_uuid}")
54
- return Result.failure(err, OrganizationId).alt(cast_exception)
54
+ return Result[OrganizationId, ValueError].failure(err).alt(cast_exception)
55
55
 
56
- return Result.success(OrganizationId(_Private(), _name, _uuid), Exception)
56
+ return Result[OrganizationId, Exception].success(OrganizationId(_Private(), _name, _uuid))
57
57
 
58
58
 
59
59
  @dataclass(frozen=True)
@@ -66,5 +66,7 @@ class OutOfScopeRoot:
66
66
  def new(nickname: str, status: str) -> ResultE[OutOfScopeRoot]:
67
67
  if not nickname:
68
68
  err = ValueError("Out-of-scope root nickname is empty")
69
- return Result.failure(err, OutOfScopeRoot).alt(cast_exception)
70
- return Result.success(OutOfScopeRoot(_Private(), nickname, status), Exception)
69
+ return Result[OutOfScopeRoot, ValueError].failure(err).alt(cast_exception)
70
+ return Result[OutOfScopeRoot, Exception].success(
71
+ OutOfScopeRoot(_Private(), nickname, status),
72
+ )
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from boto3 import (
4
+ Session,
5
+ )
6
+ from fa_purity import (
7
+ Cmd,
8
+ )
9
+ from mypy_boto3_dynamodb import (
10
+ DynamoDBClient,
11
+ DynamoDBServiceResource,
12
+ )
13
+
14
+
15
+ def new_session() -> Cmd[Session]:
16
+ # This impure procedure gets inputs (credentials) through the environment
17
+ # e.g. AWS_DEFAULT_REGION
18
+ return Cmd[Session].wrap_impure(Session)
19
+
20
+
21
+ def new_resource(session: Session) -> Cmd[DynamoDBServiceResource]:
22
+ # boto3-stubs' resource() overload set is partially unknown because
23
+ # only mypy_boto3_dynamodb is installed.
24
+ return Cmd[DynamoDBServiceResource].wrap_impure(
25
+ lambda: session.resource( # pyright: ignore[reportUnknownMemberType]
26
+ service_name="dynamodb",
27
+ use_ssl=True,
28
+ verify=True,
29
+ ),
30
+ )
31
+
32
+
33
+ def new_client(session: Session) -> Cmd[DynamoDBClient]:
34
+ # boto3-stubs' client() overload set is partially unknown because
35
+ # only mypy_boto3_dynamodb is installed.
36
+ return Cmd[DynamoDBClient].wrap_impure(
37
+ lambda: session.client("dynamodb"), # pyright: ignore[reportUnknownMemberType]
38
+ )
@@ -13,6 +13,7 @@ dependencies = [
13
13
  "fa-purity >=2.5.2, <3.0.0",
14
14
  "fluidattacks-etl-utils >=5.1.0, <6.0.0",
15
15
  "fluidattacks-utils-logger >=3.0.0, <4.0.0",
16
+ "mypy-boto3-dynamodb >=1.40.40, <2.0.0",
16
17
  ]
17
18
  description = "ASM Synchronous Data Access Layer"
18
19
  dynamic = ["version"]
@@ -33,9 +34,9 @@ dev = [
33
34
  "ruff >=0.14.1, <1.0.0",
34
35
  "botocore-stubs >=1.40.53, <2.0.0",
35
36
  "boto3-stubs >=1.40.53, <2.0.0",
36
- "mypy-boto3-dynamodb >=1.40.40, <2.0.0",
37
+ "basedpyright >=1.39.8, <2.0.0",
37
38
  ]
38
39
 
39
40
  [tool.uv]
40
41
  exclude-newer = "7 days"
41
- exclude-newer-package = { fluidattacks-core = "2099-01-01", fluid-blends = "2099-01-01" }
42
+ exclude-newer-package = { fluidattacks-core = "2099-01-02T00:00:00Z", fluid-blends = "2099-01-02T00:00:00Z" , fluidattacks-utils-logger = "2099-01-02T00:00:00Z" }
@@ -8,13 +8,8 @@ from fa_purity import (
8
8
  FrozenList,
9
9
  )
10
10
 
11
- from fluidattacks_integrates_dal._typing import (
12
- Dict,
13
- FrozenSet,
14
- )
15
-
16
11
  root = FullPathModule.assert_module("fluidattacks_integrates_dal")
17
- _dag: Dict[str, FrozenList[FrozenList[str] | str]] = {
12
+ _dag: dict[str, FrozenList[FrozenList[str] | str]] = {
18
13
  "fluidattacks_integrates_dal": (
19
14
  "_cli",
20
15
  "list_groups",
@@ -35,8 +30,8 @@ def project_dag() -> DagMap:
35
30
  return item
36
31
 
37
32
 
38
- def forbidden_allowlist() -> Dict[FullPathModule, FrozenSet[FullPathModule]]:
39
- _raw: Dict[str, FrozenSet[str]] = {}
33
+ def forbidden_allowlist() -> dict[FullPathModule, frozenset[FullPathModule]]:
34
+ _raw: dict[str, frozenset[str]] = {}
40
35
  return {
41
36
  FullPathModule.assert_module(k): frozenset(FullPathModule.assert_module(i) for i in v)
42
37
  for k, v in _raw.items()
@@ -20,7 +20,7 @@ from .arch import (
20
20
 
21
21
 
22
22
  def test_dag_creation() -> None:
23
- project_dag()
23
+ _ = project_dag()
24
24
 
25
25
 
26
26
  def test_dag() -> None:
@@ -34,7 +34,7 @@ def test_dag_completeness() -> None:
34
34
 
35
35
 
36
36
  def test_forbidden_creation() -> None:
37
- forbidden_allowlist()
37
+ _ = forbidden_allowlist()
38
38
 
39
39
 
40
40
  def test_forbidden() -> None:
@@ -32,9 +32,13 @@ def test_list_groups() -> None:
32
32
  )
33
33
  mock_group = GroupId.new("foo_group").alt(Unsafe.raise_exception).to_union()
34
34
  groups = PureIterFactory.from_list((mock_group,))
35
- mock = OrgsClient(StreamFactory.from_commands(orgs.map(Cmd.wrap_value)))
35
+ mock = OrgsClient(
36
+ StreamFactory.from_commands(orgs.map(Cmd[OrganizationId].wrap_value)),
37
+ )
36
38
  mock_2 = GroupsClient(
37
- lambda _: StreamFactory.from_commands(groups.map(Cmd.wrap_value)),
39
+ lambda _: StreamFactory.from_commands(
40
+ groups.map(Cmd[GroupId].wrap_value),
41
+ ),
38
42
  )
39
43
 
40
44
  def check(items: FrozenList[GroupId]) -> None: