scratchattach 2.1.15b0__py3-none-any.whl → 3.0.0b1__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.
Files changed (87) hide show
  1. cli/__about__.py +1 -0
  2. cli/__init__.py +26 -0
  3. cli/cmd/__init__.py +4 -0
  4. cli/cmd/group.py +127 -0
  5. cli/cmd/login.py +60 -0
  6. cli/cmd/profile.py +7 -0
  7. cli/cmd/sessions.py +5 -0
  8. cli/context.py +142 -0
  9. cli/db.py +66 -0
  10. cli/namespace.py +14 -0
  11. {scratchattach/cloud → cloud}/_base.py +112 -87
  12. {scratchattach/cloud → cloud}/cloud.py +16 -16
  13. {scratchattach/editor → editor}/__init__.py +2 -1
  14. {scratchattach/editor → editor}/asset.py +26 -14
  15. {scratchattach/editor → editor}/backpack_json.py +3 -5
  16. {scratchattach/editor → editor}/base.py +2 -4
  17. {scratchattach/editor → editor}/block.py +27 -22
  18. {scratchattach/editor → editor}/blockshape.py +1 -1
  19. {scratchattach/editor → editor}/build_defaulting.py +2 -2
  20. editor/commons.py +145 -0
  21. {scratchattach/editor → editor}/field.py +1 -1
  22. {scratchattach/editor → editor}/inputs.py +6 -3
  23. {scratchattach/editor → editor}/meta.py +10 -7
  24. {scratchattach/editor → editor}/monitor.py +10 -8
  25. {scratchattach/editor → editor}/mutation.py +68 -11
  26. {scratchattach/editor → editor}/pallete.py +1 -3
  27. {scratchattach/editor → editor}/prim.py +4 -0
  28. {scratchattach/editor → editor}/project.py +118 -16
  29. {scratchattach/editor → editor}/sprite.py +25 -15
  30. {scratchattach/editor → editor}/vlb.py +2 -2
  31. {scratchattach/eventhandlers → eventhandlers}/_base.py +1 -0
  32. {scratchattach/eventhandlers → eventhandlers}/cloud_events.py +26 -6
  33. {scratchattach/eventhandlers → eventhandlers}/cloud_recorder.py +4 -4
  34. {scratchattach/eventhandlers → eventhandlers}/cloud_requests.py +139 -54
  35. {scratchattach/eventhandlers → eventhandlers}/cloud_server.py +6 -3
  36. {scratchattach/eventhandlers → eventhandlers}/cloud_storage.py +1 -2
  37. eventhandlers/filterbot.py +163 -0
  38. other/other_apis.py +598 -0
  39. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +7 -11
  40. scratchattach-3.0.0b1.dist-info/RECORD +79 -0
  41. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +1 -1
  42. scratchattach-3.0.0b1.dist-info/entry_points.txt +2 -0
  43. scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
  44. {scratchattach/site → site}/_base.py +32 -5
  45. site/activity.py +426 -0
  46. {scratchattach/site → site}/alert.py +4 -5
  47. {scratchattach/site → site}/backpack_asset.py +2 -1
  48. {scratchattach/site → site}/classroom.py +80 -73
  49. {scratchattach/site → site}/cloud_activity.py +43 -29
  50. {scratchattach/site → site}/comment.py +86 -100
  51. {scratchattach/site → site}/forum.py +8 -4
  52. site/placeholder.py +132 -0
  53. {scratchattach/site → site}/project.py +228 -122
  54. {scratchattach/site → site}/session.py +156 -71
  55. {scratchattach/site → site}/studio.py +139 -46
  56. site/typed_dicts.py +151 -0
  57. {scratchattach/site → site}/user.py +511 -215
  58. {scratchattach/utils → utils}/commons.py +12 -4
  59. {scratchattach/utils → utils}/encoder.py +7 -4
  60. {scratchattach/utils → utils}/enums.py +1 -0
  61. {scratchattach/utils → utils}/exceptions.py +36 -2
  62. utils/optional_async.py +154 -0
  63. utils/requests.py +306 -0
  64. scratchattach/__init__.py +0 -29
  65. scratchattach/editor/commons.py +0 -273
  66. scratchattach/eventhandlers/filterbot.py +0 -161
  67. scratchattach/other/other_apis.py +0 -284
  68. scratchattach/site/activity.py +0 -382
  69. scratchattach/utils/requests.py +0 -93
  70. scratchattach-2.1.15b0.dist-info/RECORD +0 -66
  71. scratchattach-2.1.15b0.dist-info/top_level.txt +0 -1
  72. {scratchattach/cloud → cloud}/__init__.py +0 -0
  73. {scratchattach/editor → editor}/code_translation/__init__.py +0 -0
  74. {scratchattach/editor → editor}/code_translation/parse.py +0 -0
  75. {scratchattach/editor → editor}/comment.py +0 -0
  76. {scratchattach/editor → editor}/extension.py +0 -0
  77. {scratchattach/editor → editor}/twconfig.py +0 -0
  78. {scratchattach/eventhandlers → eventhandlers}/__init__.py +0 -0
  79. {scratchattach/eventhandlers → eventhandlers}/combine.py +0 -0
  80. {scratchattach/eventhandlers → eventhandlers}/message_events.py +0 -0
  81. {scratchattach/other → other}/__init__.py +0 -0
  82. {scratchattach/other → other}/project_json_capabilities.py +0 -0
  83. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
  84. {scratchattach/site → site}/__init__.py +0 -0
  85. {scratchattach/site → site}/browser_cookie3_stub.py +0 -0
  86. {scratchattach/site → site}/browser_cookies.py +0 -0
  87. {scratchattach/utils → utils}/__init__.py +0 -0
@@ -2,20 +2,21 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ import string
5
6
  import warnings
6
7
  from io import BytesIO, TextIOWrapper
7
8
  from typing import Optional, Iterable, Generator, BinaryIO
9
+ from typing_extensions import deprecated
8
10
  from zipfile import ZipFile
9
11
 
10
- from . import base, meta, extension, monitor, sprite, asset, vlb, twconfig, comment, commons
12
+ from . import base, meta, extension, monitor, sprite, asset, vlb, twconfig, comment, commons, mutation
11
13
  from scratchattach.site import session
12
- from scratchattach.site.project import get_project
13
14
  from scratchattach.utils import exceptions
14
15
 
15
16
 
16
17
  class Project(base.JSONExtractable):
17
18
  """
18
- sa.editor's equivalent of the ProjectBody. Represents the editor contents of a scratch project
19
+ A Project (body). Represents the editor contents of a scratch project
19
20
  """
20
21
  def __init__(self, _name: Optional[str] = None, _meta: Optional[meta.Meta] = None, _extensions: Iterable[extension.Extension] = (),
21
22
  _monitors: Iterable[monitor.Monitor] = (), _sprites: Iterable[sprite.Sprite] = (), *,
@@ -31,7 +32,7 @@ class Project(base.JSONExtractable):
31
32
  self.name = _name
32
33
 
33
34
  self.meta = _meta
34
- self.extensions = _extensions
35
+ self.extensions: list[extension.Extension] = list(_extensions)
35
36
  self.monitors = list(_monitors)
36
37
  self.sprites = list(_sprites)
37
38
 
@@ -69,11 +70,12 @@ class Project(base.JSONExtractable):
69
70
  return _ret
70
71
 
71
72
  @property
72
- def stage(self) -> sprite.Sprite:
73
+ def stage(self) -> Optional[sprite.Sprite]:
73
74
  for _sprite in self.sprites:
74
75
  if _sprite.is_stage:
75
76
  return _sprite
76
77
  warnings.warn(f"Could not find stage for {self.name}")
78
+ return None
77
79
 
78
80
  def to_json(self) -> dict:
79
81
  _json = {
@@ -208,22 +210,25 @@ class Project(base.JSONExtractable):
208
210
  return project
209
211
 
210
212
  @staticmethod
213
+ @deprecated("Use get_project(id).body() instead")
211
214
  def from_id(project_id: int, _name: Optional[str] = None):
212
- _proj = get_project(project_id)
213
- data = json.loads(_proj.get_json())
215
+ raise Exception("This method is deprecated")
216
+ # _proj = get_project(project_id)
217
+ # data = json.loads(_proj.get_json())
214
218
 
215
- if _name is None:
216
- _name = _proj.title
217
- _name = str(_name)
219
+ # if _name is None:
220
+ # _name = _proj.title
221
+ # _name = str(_name)
218
222
 
219
- _proj = Project.from_json(data)
220
- _proj.name = _name
221
- return _proj
223
+ # _proj = Project.from_json(data)
224
+ # _proj.name = _name
225
+ # return _proj
222
226
 
223
227
  def find_vlb(self, value: str | None, by: str = "name",
224
- multiple: bool = False) -> vlb.Variable | vlb.List | vlb.Broadcast | list[
225
- vlb.Variable | vlb.List | vlb.Broadcast]:
226
- _ret = []
228
+ multiple: bool = False) -> Optional[vlb.Variable | vlb.List | vlb.Broadcast | list[
229
+ vlb.Variable | vlb.List | vlb.Broadcast]]:
230
+
231
+ _ret: list[vlb.Variable | vlb.List | vlb.Broadcast] = []
227
232
  for _sprite in self.sprites:
228
233
  val = _sprite.find_vlb(value, by, multiple)
229
234
  if multiple:
@@ -231,9 +236,12 @@ class Project(base.JSONExtractable):
231
236
  else:
232
237
  if val is not None:
233
238
  return val
239
+
234
240
  if multiple:
235
241
  return _ret
236
242
 
243
+ return None
244
+
237
245
  def find_sprite(self, value: str | None, by: str = "name",
238
246
  multiple: bool = False) -> sprite.Sprite | list[sprite.Sprite]:
239
247
  _ret = []
@@ -277,3 +285,97 @@ class Project(base.JSONExtractable):
277
285
  _monitor.project = self
278
286
  _monitor.reporter_id = self.new_id
279
287
  self.monitors.append(_monitor)
288
+ return _monitor
289
+
290
+ def obfuscate(self, *, goto_origin: bool=True) -> None:
291
+ """
292
+ Randomly set all the variable names etc. Do not upload this project to the scratch website, as it is
293
+ against the community guidelines.
294
+ """
295
+ # this code is an embarrassing mess. If certain features are added to sa.editor, then it could become a lot cleaner
296
+ chars = string.ascii_letters + string.digits + string.punctuation
297
+
298
+ def b10_to_cbase(b10: int | float):
299
+ ret = ''
300
+ new_base = len(chars)
301
+ while b10 >= 1:
302
+ ret = chars[int(b10 % new_base)] + ret
303
+ b10 /= new_base
304
+
305
+ return ret
306
+
307
+ used = 0
308
+
309
+ def rand():
310
+ nonlocal used
311
+ used += 1
312
+
313
+ return b10_to_cbase(used)
314
+
315
+ for _sprite in self.sprites:
316
+ procedure_mappings: dict[str, str] = {}
317
+ argument_mappings: dict[str, str] = {}
318
+ done_args: list[mutation.Argument] = []
319
+
320
+ for _variable in _sprite.variables:
321
+ _variable.name = rand()
322
+ for _list in _sprite.lists:
323
+ _list.name = rand()
324
+ # don't rename broadcasts as these can be dynamically called
325
+
326
+ def arg_get(name: str) -> str:
327
+ if name not in argument_mappings:
328
+ argument_mappings[name] = rand()
329
+ return argument_mappings[name]
330
+
331
+ for _block in _sprite.blocks.values():
332
+ if goto_origin:
333
+ _block.x, _block.y = 0, 0
334
+
335
+ # TODO: Add special handling for turbowarp debugger blocks
336
+ if _block.opcode in ("procedures_call", "procedures_prototype", "procedures_definition"):
337
+ if _block.mutation is None:
338
+ continue
339
+
340
+ proccode = _block.mutation.proc_code
341
+ if proccode is None:
342
+ continue
343
+
344
+ if proccode not in procedure_mappings:
345
+ parsed_ppc = _block.mutation.parsed_proc_code
346
+
347
+ if parsed_ppc is None:
348
+ continue
349
+
350
+ new: list[str | mutation.ArgumentType] = []
351
+ for item in parsed_ppc:
352
+ if isinstance(item, str):
353
+ item = rand()
354
+
355
+ new.append(item)
356
+
357
+ new_proccode = mutation.construct_proccode(*new)
358
+ procedure_mappings[proccode] = new_proccode
359
+
360
+ _block.mutation.proc_code = procedure_mappings[proccode]
361
+
362
+ assert _block.mutation.arguments is not None
363
+ for arg in _block.mutation.arguments:
364
+ if arg in done_args:
365
+ continue
366
+ done_args.append(arg)
367
+
368
+ arg.name = arg_get(arg.name)
369
+
370
+ # print(_block, _block.mutation)
371
+ elif _block.opcode in ("argument_reporter_string_number", "argument_reporter_boolean"):
372
+ arg_name = _block.fields["VALUE"].value
373
+ assert isinstance(arg_name, str)
374
+ _block.fields["VALUE"].value = arg_get(arg_name)
375
+
376
+
377
+ # print(argument_mappings)
378
+
379
+ if goto_origin:
380
+ for _comment in _sprite.comments:
381
+ _comment.x, _comment.y = 0, 0
@@ -102,6 +102,11 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
102
102
  self.link_prims()
103
103
  self.link_blocks()
104
104
  self.link_comments()
105
+ self.link_vlbs()
106
+
107
+ def link_vlbs(self):
108
+ for _vlb in self.vlbs:
109
+ _vlb.sprite = self
105
110
 
106
111
  def link_prims(self):
107
112
  """
@@ -278,18 +283,18 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
278
283
  _tempo, _video_state, _video_transparency, _text_to_speech_language = (None,) * 4
279
284
  _visible, _x, _y, _size, _direction, _draggable, _rotation_style = (None,) * 7
280
285
  if _is_stage:
281
- _tempo = data["tempo"]
282
- _video_state = data["videoState"]
283
- _video_transparency = data["videoTransparency"]
284
- _text_to_speech_language = data["textToSpeechLanguage"]
286
+ _tempo = data.get("tempo", 0)
287
+ _video_state = data.get("videoState", "off")
288
+ _video_transparency = data.get("videoTransparency", 0)
289
+ _text_to_speech_language = data.get("textToSpeechLanguage", "en")
285
290
  else:
286
291
  _visible = data["visible"]
287
- _x = data["x"]
288
- _y = data["y"]
289
- _size = data["size"]
290
- _direction = data["direction"]
291
- _draggable = data["draggable"]
292
- _rotation_style = data["rotationStyle"]
292
+ _x = data.get("x", 0)
293
+ _y = data.get("y", 0)
294
+ _size = data.get("size", 100)
295
+ _direction = data.get("direction", 90)
296
+ _draggable = data.get("draggable", False)
297
+ _rotation_style = data.get("rotationStyle", "all-around")
293
298
 
294
299
  return Sprite(_is_stage, _name, _current_costume, _layer_order, _volume, _broadcasts, _variables, _lists,
295
300
  _costumes,
@@ -340,7 +345,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
340
345
  return _json
341
346
 
342
347
  # Finding/getting from list/dict attributes
343
- def find_asset(self, value: str, by: str = "name", multiple: bool = False, a_type: Optional[type]=None) -> asset.Asset | asset.Sound | asset.Costume | list[asset.Asset | asset.Sound | asset.Costume]:
348
+ def find_asset(self, value: str, by: str = "name", multiple: bool = False, a_type: Optional[type]=None) -> None | asset.Asset | asset.Sound | asset.Costume | list[asset.Asset | asset.Sound | asset.Costume]:
344
349
  if a_type is None:
345
350
  a_type = asset.Asset
346
351
 
@@ -361,8 +366,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
361
366
 
362
367
  if multiple:
363
368
  return _ret
369
+ return None
364
370
 
365
- def find_variable(self, value: str, by: str = "name", multiple: bool = False) -> vlb.Variable | list[vlb.Variable]:
371
+ def find_variable(self, value: str, by: str = "name", multiple: bool = False) -> None | vlb.Variable | list[vlb.Variable]:
366
372
  _ret = []
367
373
  by = by.lower()
368
374
  for _variable in self.variables + self._local_globals:
@@ -389,8 +395,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
389
395
 
390
396
  if multiple:
391
397
  return _ret
398
+ return None
392
399
 
393
- def find_list(self, value: str, by: str = "name", multiple: bool = False) -> vlb.List | list[vlb.List]:
400
+ def find_list(self, value: str, by: str = "name", multiple: bool = False) -> None | vlb.List | list[vlb.List]:
394
401
  _ret = []
395
402
  by = by.lower()
396
403
  for _list in self.lists + self._local_globals:
@@ -416,8 +423,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
416
423
 
417
424
  if multiple:
418
425
  return _ret
426
+ return None
419
427
 
420
- def find_broadcast(self, value: str, by: str = "name", multiple: bool = False) -> vlb.Broadcast | list[
428
+ def find_broadcast(self, value: str, by: str = "name", multiple: bool = False) -> None | vlb.Broadcast | list[
421
429
  vlb.Broadcast]:
422
430
  _ret = []
423
431
  by = by.lower()
@@ -444,6 +452,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
444
452
 
445
453
  if multiple:
446
454
  return _ret
455
+ return None
447
456
 
448
457
  def find_vlb(self, value: str, by: str = "name",
449
458
  multiple: bool = False) -> vlb.Variable | vlb.List | vlb.Broadcast | list[
@@ -461,7 +470,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
461
470
  return _ret
462
471
  return self.find_broadcast(value, by)
463
472
 
464
- def find_block(self, value: str | Any, by: str, multiple: bool = False) -> block.Block | prim.Prim | list[
473
+ def find_block(self, value: str | Any, by: str, multiple: bool = False) -> None | block.Block | prim.Prim | list[
465
474
  block.Block | prim.Prim]:
466
475
  _ret = []
467
476
  by = by.lower()
@@ -507,6 +516,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
507
516
 
508
517
  if multiple:
509
518
  return _ret
519
+ return None
510
520
 
511
521
  def export(self, fp: Optional[str] = None, *, export_as_zip: bool = True):
512
522
  if fp is None:
@@ -73,7 +73,7 @@ class List(base.NamedIDComponent):
73
73
  https://en.scratch-wiki.info/wiki/Scratch_File_Format#Targets:~:text=lists,as%20an%20array
74
74
  """
75
75
  if _value is None:
76
- _value = []
76
+ _value: list[str | int | float] = []
77
77
 
78
78
  self.value = _value
79
79
  super().__init__(_id, _name, _sprite)
@@ -91,7 +91,7 @@ class List(base.NamedIDComponent):
91
91
 
92
92
  return List(_id, _name, _value)
93
93
 
94
- def to_json(self) -> tuple[str, tuple[str, str | int | float, bool] | tuple[str, str | int | float]]:
94
+ def to_json(self) -> tuple[str, list[str | int | float]]:
95
95
  """
96
96
  Returns List data as a tuple
97
97
  """
@@ -17,6 +17,7 @@ class BaseEventHandler(ABC):
17
17
  self.running = False
18
18
  self._events = defaultdict(list)
19
19
  self._threaded_events = defaultdict(list)
20
+ print(f"{self._threaded_events=}")
20
21
 
21
22
  def start(self, *, thread=True, ignore_exceptions=True):
22
23
  """
@@ -1,8 +1,11 @@
1
1
  """CloudEvents class"""
2
2
  from __future__ import annotations
3
3
 
4
+ import traceback
5
+
4
6
  from scratchattach.cloud import _base
5
7
  from ._base import BaseEventHandler
8
+ from scratchattach.utils import exceptions
6
9
  from scratchattach.site import cloud_activity
7
10
  import time
8
11
  import json
@@ -18,6 +21,7 @@ class CloudEvents(BaseEventHandler):
18
21
  self._session = cloud._session
19
22
  self.source_stream = cloud.create_event_stream()
20
23
  self.startup_time = time.time() * 1000
24
+ self.subsequent_reconnects = 0
21
25
 
22
26
  def disconnect(self):
23
27
  self.source_stream.close()
@@ -31,25 +35,34 @@ class CloudEvents(BaseEventHandler):
31
35
 
32
36
  if self.running is False:
33
37
  return
38
+
39
+ # TODO: refactor this method. It works, but is hard to read
34
40
  while True:
35
41
  try:
36
42
  while True:
43
+ # print("Checking for more events")
37
44
  for data in self.source_stream.read():
45
+ # print(f"Got event {data}")
46
+ self.subsequent_reconnects = 0
38
47
  try:
39
48
  _a = cloud_activity.CloudActivity(timestamp=time.time()*1000, _session=self._session, cloud=self.cloud)
40
49
  if _a.timestamp < self.startup_time + 500: # catch the on_connect message sent by TurboWarp's (and sometimes Scratch's) cloud server
50
+ # print(f"Skipped as {_a.timestamp} < {self.startup_time + 500}")
41
51
  continue
42
52
  data["variable_name"] = data["name"]
43
53
  data["name"] = data["variable_name"].replace("☁ ", "")
44
54
  _a._update_from_dict(data)
55
+ # print(f"sending event {_a}")
45
56
  self.call_event("on_"+_a.type, [_a])
46
57
  except Exception as e:
58
+ print(f"Cloud events _updated ignored: {e} {traceback.format_exc()}")
47
59
  pass
48
60
  except Exception:
49
- print("CloudEvents: Disconnected. Reconnecting ...", time.time())
61
+ self.subsequent_reconnects += 1
50
62
  time.sleep(0.1) # cooldown
51
63
 
52
- print("CloudEvents: Reconnected.", time.time())
64
+ if self.subsequent_reconnects >= 5:
65
+ print(f"Warning: {self.subsequent_reconnects} subsequent cloud disconnects. Cloud may be down, causing CloudEvents to not call events.")
53
66
  self.call_event("on_reconnect", [])
54
67
 
55
68
  class ManualCloudLogEvents:
@@ -63,22 +76,25 @@ class ManualCloudLogEvents:
63
76
  self.source_cloud = cloud
64
77
  self._session = cloud._session
65
78
  self.last_timestamp = 0
66
-
79
+ self.subsequent_failed_log_fetches = 0
80
+
67
81
  def update(self) -> Iterator[tuple[str, list[cloud_activity.CloudActivity]]]:
68
82
  """
69
83
  Update once and yield all packets
70
84
  """
71
85
  try:
72
86
  data = self.source_cloud.logs(limit=25)
87
+ self.subsequent_failed_log_fetches = 0
73
88
  for _a in data[::-1]:
74
89
  if _a.timestamp <= self.last_timestamp:
75
90
  continue
76
91
  self.last_timestamp = _a.timestamp
77
92
  yield ("on_"+_a.type, [_a])
78
93
  except Exception:
79
- pass
94
+ self.subsequent_failed_log_fetches += 1
95
+ if self.subsequent_failed_log_fetches == 20:
96
+ print("Warning: 20 subsequent clouddata log fetches failed. Scrach's cloud logs may be down, causing CloudLogEvents to not call events.")
80
97
 
81
-
82
98
  class CloudLogEvents(BaseEventHandler):
83
99
  """
84
100
  Class that calls events on cloud updates that are received from a clouddata log.
@@ -95,7 +111,11 @@ class CloudLogEvents(BaseEventHandler):
95
111
  self.manual_cloud_log_events = ManualCloudLogEvents(cloud)
96
112
 
97
113
  def _updater(self):
98
- logs = self.source_cloud.logs(limit=25)
114
+ try:
115
+ logs = self.source_cloud.logs(limit=25)
116
+ except exceptions.FetchError:
117
+ logs = []
118
+
99
119
  self.last_timestamp = 0
100
120
  if len(logs) != 0:
101
121
  self.last_timestamp = logs[0].timestamp
@@ -1,14 +1,14 @@
1
1
  """CloudRecorder class (used by ScratchCloud, TwCloud and other classes inheriting from BaseCloud to deliver cloud var values)"""
2
2
  from __future__ import annotations
3
3
 
4
+ from typing import Optional, Any
5
+
4
6
  from .cloud_events import CloudEvents
5
- from typing import Optional
6
7
 
7
8
 
8
9
  class CloudRecorder(CloudEvents):
9
- def __init__(self, cloud, *, initial_values: Optional[dict] = None):
10
- if initial_values is None:
11
- initial_values = {}
10
+ def __init__(self, cloud, *, initial_values: Optional[dict[str, Any]] = None):
11
+ initial_values = initial_values or {}
12
12
 
13
13
  super().__init__(cloud)
14
14
  self.cloud_values = initial_values