scratchattach 2.1.14__py3-none-any.whl → 3.0.0b0__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 (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -306
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -825
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.14.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,475 +0,0 @@
1
- """Project JSON reading and editing capabilities.
2
- This code is still in BETA, there are still bugs and potential consistency issues to be fixed. New features will be added."""
3
-
4
- # Note: You may want to make this into multiple files for better organisation
5
- from __future__ import annotations
6
-
7
- import hashlib
8
- import json
9
- import random
10
- import string
11
- import zipfile
12
- from abc import ABC, abstractmethod
13
- from scratchattach.utils import exceptions
14
- from scratchattach.utils.commons import empty_project_json
15
- from scratchattach.utils.requests import requests
16
- # noinspection PyPep8Naming
17
- def load_components(json_data: list, ComponentClass: type, target_list: list):
18
- for element in json_data:
19
- component = ComponentClass()
20
- component.from_json(element)
21
- target_list.append(component)
22
- class ProjectBody:
23
- class BaseProjectBodyComponent(ABC):
24
- def __init__(self, **entries):
25
- # Attributes every object needs to have:
26
- self.id = None
27
- # Update attributes from entries dict:
28
- self.__dict__.update(entries)
29
- @abstractmethod
30
- def from_json(self, data: dict):
31
- pass
32
- @abstractmethod
33
- def to_json(self):
34
- pass
35
- def _generate_new_id(self):
36
- """
37
- Generates a new id and updates the id.
38
- Warning:
39
- When done on Block objects, the next_id attribute of the parent block and the parent_id attribute of the next block will NOT be updated by this method.
40
- """
41
- self.id = ''.join(random.choices(string.ascii_letters + string.digits, k=20))
42
- class Block(BaseProjectBodyComponent):
43
- # Thanks to @MonkeyBean2 for some scripts
44
- def from_json(self, data: dict):
45
- self.opcode = data["opcode"] # The name of the block
46
- self.next_id = data.get("next", None) # The id of the block attached below this block
47
- self.parent_id = data.get("parent", None) # The id of the block that this block is attached to
48
- self.input_data = data.get("inputs", None) # The blocks inside of the block (if the block is a loop or an if clause for example)
49
- self.fields = data.get("fields", None) # The values inside the block's inputs
50
- self.shadow = data.get("shadow", False) # Whether the block is displayed with a shadow
51
- self.topLevel = data.get("topLevel", False) # Whether the block has no parent
52
- self.mutation = data.get("mutation", None) # For custom blocks
53
- self.x = data.get("x", None) # x position if topLevel
54
- self.y = data.get("y", None) # y position if topLevel
55
- def to_json(self):
56
- output = {"opcode": self.opcode, "next": self.next_id, "parent": self.parent_id, "inputs": self.input_data,
57
- "fields": self.fields, "shadow": self.shadow, "topLevel": self.topLevel,
58
- "mutation": self.mutation, "x": self.x, "y": self.y}
59
- return {k: v for k, v in output.items() if v}
60
- def attached_block(self):
61
- return self.sprite.block_by_id(self.next_id)
62
- def previous_block(self):
63
- return self.sprite.block_by_id(self.parent_id)
64
- def top_level_block(self):
65
- block = self
66
- return block
67
- def previous_chain(self):
68
- # to implement: a method that detects circular block chains (to make sure this method terminates)
69
- chain = []
70
- block = self
71
- while block.parent_id is not None:
72
- block = block.previous_block()
73
- chain.insert(0, block)
74
- return chain
75
- def attached_chain(self):
76
- chain = []
77
- block = self
78
- while block.next_id is not None:
79
- block = block.attached_block()
80
- chain.append(block)
81
- return chain
82
- def complete_chain(self):
83
- return self.previous_chain() + [self] + self.attached_chain()
84
- def duplicate_single_block(self):
85
- new_block = ProjectBody.Block(**self.__dict__)
86
- new_block.parent_id = None
87
- new_block.next_id = None
88
- new_block._generate_new_id()
89
- self.sprite.blocks.append(new_block)
90
- return new_block
91
- def duplicate_chain(self):
92
- blocks_to_dupe = [self] + self.attached_chain()
93
- duped = []
94
- for i in range(len(blocks_to_dupe)):
95
- new_block = ProjectBody.Block(**blocks_to_dupe[i].__dict__)
96
- new_block.parent_id = None
97
- new_block.next_id = None
98
- new_block._generate_new_id()
99
- if i != 0:
100
- new_block.parent_id = duped[i - 1].id
101
- duped[i - 1].next_id = new_block.id
102
- duped.append(new_block)
103
- self.sprite.blocks += duped
104
- return duped
105
- def _reattach(self, new_parent_id, new_next_id_of_old_parent):
106
- if self.parent_id is not None:
107
- old_parent_block = self.sprite.block_by_id(self.parent_id)
108
- self.sprite.blocks.remove(old_parent_block)
109
- old_parent_block.next_id = new_next_id_of_old_parent
110
- self.sprite.blocks.append(old_parent_block)
111
- self.parent_id = new_parent_id
112
- if self.parent_id is not None:
113
- new_parent_block = self.sprite.block_by_id(self.parent_id)
114
- self.sprite.blocks.remove(new_parent_block)
115
- new_parent_block.next_id = self.id
116
- self.sprite.blocks.append(new_parent_block)
117
- self.topLevel = new_parent_id is None
118
- def reattach_single_block(self, new_parent_id):
119
- old_parent_id = str(self.parent_id)
120
- self._reattach(new_parent_id, self.next_id)
121
- if self.next_id is not None:
122
- old_next_block = self.sprite.block_by_id(self.next_id)
123
- self.sprite.blocks.remove(old_next_block)
124
- old_next_block.parent_id = old_parent_id
125
- self.sprite.blocks.append(old_next_block)
126
- self.next_id = None
127
- def reattach_chain(self, new_parent_id):
128
- self._reattach(new_parent_id, None)
129
- def delete_single_block(self):
130
- self.sprite.blocks.remove(self)
131
- self.reattach_single_block(None, self.next_id)
132
- def delete_chain(self):
133
- self.sprite.blocks.remove(self)
134
- self.reattach_chain(None)
135
- to_delete = self.attached_chain()
136
- for block in to_delete:
137
- self.sprite.blocks.remove(block)
138
- def inputs_as_blocks(self):
139
- if self.input_data is None:
140
- return None
141
- inputs = []
142
- for input in self.input_data:
143
- inputs.append(self.sprite.block_by_id(self.input_data[input][1]))
144
- class Sprite(BaseProjectBodyComponent):
145
- def from_json(self, data: dict):
146
- self.isStage = data["isStage"]
147
- self.name = data["name"]
148
- self.id = self.name # Sprites are uniquely identifiable through their name
149
- self.variables = []
150
- for variable_id in data["variables"]: # self.lists is a dict with the list_id as key and info as value
151
- pvar = ProjectBody.Variable(id=variable_id)
152
- pvar.from_json(data["variables"][variable_id])
153
- self.variables.append(pvar)
154
- self.lists = []
155
- for list_id in data["lists"]: # self.lists is a dict with the list_id as key and info as value
156
- plist = ProjectBody.List(id=list_id)
157
- plist.from_json(data["lists"][list_id])
158
- self.lists.append(plist)
159
- self.broadcasts = data["broadcasts"]
160
- self.blocks = []
161
- for block_id in data["blocks"]: # self.blocks is a dict with the block_id as key and block content as value
162
- if isinstance(data["blocks"][block_id],
163
- dict): # Sometimes there is a weird list at the end of the blocks list. This list is ignored
164
- block = ProjectBody.Block(id=block_id, sprite=self)
165
- block.from_json(data["blocks"][block_id])
166
- self.blocks.append(block)
167
- self.comments = data["comments"]
168
- self.currentCostume = data["currentCostume"]
169
- self.costumes = []
170
- load_components(data["costumes"], ProjectBody.Asset, self.costumes) # load lists
171
- self.sounds = []
172
- load_components(data["sounds"], ProjectBody.Asset, self.sounds) # load lists
173
- self.volume = data["volume"]
174
- self.layerOrder = data["layerOrder"]
175
- if self.isStage:
176
- self.tempo = data.get("tempo", None)
177
- self.videoTransparency = data.get("videoTransparency", None)
178
- self.videoState = data.get("videoState", None)
179
- self.textToSpeechLanguage = data.get("textToSpeechLanguage", None)
180
- else:
181
- self.visible = data.get("visible", None)
182
- self.x = data.get("x", None)
183
- self.y = data.get("y", None)
184
- self.size = data.get("size", None)
185
- self.direction = data.get("direction", None)
186
- self.draggable = data.get("draggable", None)
187
- self.rotationStyle = data.get("rotationStyle", None)
188
- def to_json(self):
189
- return_data = dict(self.__dict__)
190
- if "projectBody" in return_data:
191
- return_data.pop("projectBody")
192
- return_data.pop("id")
193
- return_data["variables"] = {}
194
- for variable in self.variables:
195
- return_data["variables"][variable.id] = variable.to_json()
196
- return_data["lists"] = {}
197
- for plist in self.lists:
198
- return_data["lists"][plist.id] = plist.to_json()
199
- return_data["blocks"] = {}
200
- for block in self.blocks:
201
- return_data["blocks"][block.id] = block.to_json()
202
- return_data["costumes"] = [custome.to_json() for custome in self.costumes]
203
- return_data["sounds"] = [sound.to_json() for sound in self.sounds]
204
- return return_data
205
- def variable_by_id(self, variable_id):
206
- matching = list(filter(lambda x: x.id == variable_id, self.variables))
207
- if matching == []:
208
- return None
209
- return matching[0]
210
- def list_by_id(self, list_id):
211
- matching = list(filter(lambda x: x.id == list_id, self.lists))
212
- if matching == []:
213
- return None
214
- return matching[0]
215
- def variable_by_name(self, variable_name):
216
- matching = list(filter(lambda x: x.name == variable_name, self.variables))
217
- if matching == []:
218
- return None
219
- return matching[0]
220
- def list_by_name(self, list_name):
221
- matching = list(filter(lambda x: x.name == list_name, self.lists))
222
- if matching == []:
223
- return None
224
- return matching[0]
225
- def block_by_id(self, block_id):
226
- matching = list(filter(lambda x: x.id == block_id, self.blocks))
227
- if matching == []:
228
- return None
229
- return matching[0]
230
- # -- Functions to modify project contents --
231
- def create_sound(self, asset_content, *, name="new sound", dataFormat="mp3", rate=4800, sampleCount=4800):
232
- data = asset_content if isinstance(asset_content, bytes) else open(asset_content, "rb").read()
233
- new_asset_id = hashlib.md5(data).hexdigest()
234
- new_asset = ProjectBody.Asset(assetId=new_asset_id, name=name, id=new_asset_id, dataFormat=dataFormat,
235
- rate=rate, sampleCound=sampleCount, md5ext=new_asset_id + "." + dataFormat,
236
- filename=new_asset_id + "." + dataFormat)
237
- self.sounds.append(new_asset)
238
- if not hasattr(self, "projectBody"):
239
- print(
240
- "Warning: Since there's no project body connected to this object, the new sound asset won't be uploaded to Scratch")
241
- elif self.projectBody._session is None:
242
- print(
243
- "Warning: Since there's no login connected to this object, the new sound asset won't be uploaded to Scratch")
244
- else:
245
- self._session.upload_asset(data, asset_id=new_asset_id, file_ext=dataFormat)
246
- return new_asset
247
- def create_costume(self, asset_content, *, name="new costume", dataFormat="svg", rotationCenterX=0,
248
- rotationCenterY=0):
249
- data = asset_content if isinstance(asset_content, bytes) else open(asset_content, "rb").read()
250
- new_asset_id = hashlib.md5(data).hexdigest()
251
- new_asset = ProjectBody.Asset(assetId=new_asset_id, name=name, id=new_asset_id, dataFormat=dataFormat,
252
- rotationCenterX=rotationCenterX, rotationCenterY=rotationCenterY,
253
- md5ext=new_asset_id + "." + dataFormat,
254
- filename=new_asset_id + "." + dataFormat)
255
- self.costumes.append(new_asset)
256
- if not hasattr(self, "projectBody"):
257
- print(
258
- "Warning: Since there's no project body connected to this object, the new costume asset won't be uploaded to Scratch")
259
- elif self.projectBody._session is None:
260
- print(
261
- "Warning: Since there's no login connected to this object, the new costume asset won't be uploaded to Scratch")
262
- else:
263
- self._session.upload_asset(data, asset_id=new_asset_id, file_ext=dataFormat)
264
- return new_asset
265
- def create_variable(self, name, *, value=0, is_cloud=False):
266
- new_var = ProjectBody.Variable(name=name, value=value, is_cloud=is_cloud)
267
- self.variables.append(new_var)
268
- return new_var
269
- def create_list(self, name, *, value=[]):
270
- new_list = ProjectBody.List(name=name, value=value)
271
- self.lists.append(new_list)
272
- return new_list
273
- def add_block(self, block, *, parent_id=None):
274
- block.parent_id = None
275
- block.next_id = None
276
- if parent_id is not None:
277
- block.reattach_single_block(parent_id)
278
- self.blocks.append(block)
279
- def add_block_chain(self, block_chain, *, parent_id=None):
280
- parent = parent_id
281
- for block in block_chain:
282
- self.add_block(block, parent_id=parent)
283
- parent = str(block.id)
284
- class Variable(BaseProjectBodyComponent):
285
- def __init__(self, **entries):
286
- super().__init__(**entries)
287
- if self.id is None:
288
- self._generate_new_id()
289
- def from_json(self, data: list):
290
- self.name = data[0]
291
- self.saved_value = data[1]
292
- self.is_cloud = len(data) == 3
293
- def to_json(self):
294
- if self.is_cloud:
295
- return [self.name, self.saved_value, True]
296
- else:
297
- return [self.name, self.saved_value]
298
- def make_cloud_variable(self):
299
- self.is_cloud = True
300
- class List(BaseProjectBodyComponent):
301
- def __init__(self, **entries):
302
- super().__init__(**entries)
303
- if self.id is None:
304
- self._generate_new_id()
305
- def from_json(self, data: list):
306
- self.name = data[0]
307
- self.saved_content = data[1]
308
- def to_json(self):
309
- return [self.name, self.saved_content]
310
- class Monitor(BaseProjectBodyComponent):
311
- def from_json(self, data: dict):
312
- self.__dict__.update(data)
313
- def to_json(self):
314
- return_data = dict(self.__dict__)
315
- if "projectBody" in return_data:
316
- return_data.pop("projectBody")
317
- return return_data
318
- def target(self):
319
- if not hasattr(self, "projectBody"):
320
- print("Can't get represented object because the origin projectBody of this monitor is not saved")
321
- return
322
- if "VARIABLE" in self.params:
323
- return self.projectBody.sprite_by_name(self.spriteName).variable_by_name(self.params["VARIABLE"])
324
- if "LIST" in self.params:
325
- return self.projectBody.sprite_by_name(self.spriteName).list_by_name(self.params["LIST"])
326
- class Asset(BaseProjectBodyComponent):
327
- def from_json(self, data: dict):
328
- self.__dict__.update(data)
329
- self.id = self.assetId
330
- self.filename = self.md5ext
331
- self.download_url = f"https://assets.scratch.mit.edu/internalapi/asset/{self.filename}"
332
- def to_json(self):
333
- return_data = dict(self.__dict__)
334
- return_data.pop("filename")
335
- return_data.pop("id")
336
- return_data.pop("download_url")
337
- return return_data
338
- def download(self, *, filename=None, dir=""):
339
- if not (dir.endswith("/") or dir.endswith("\\")):
340
- dir = dir + "/"
341
- try:
342
- if filename is None:
343
- filename = str(self.filename)
344
- response = requests.get(
345
- self.download_url,
346
- timeout=10,
347
- )
348
- open(f"{dir}{filename}", "wb").write(response.content)
349
- except Exception:
350
- raise (
351
- exceptions.FetchError(
352
- "Failed to download asset"
353
- )
354
- )
355
- def __init__(self, *, sprites=[], monitors=[], extensions=[], meta=[{"agent": None}], _session=None):
356
- # sprites are called "targets" in the initial API response
357
- self.sprites = sprites
358
- self.monitors = monitors
359
- self.extensions = extensions
360
- self.meta = meta
361
- self._session = _session
362
- def from_json(self, data: dict):
363
- """
364
- Imports the project data from a dict that contains the raw project json
365
- """
366
- # Load sprites:
367
- self.sprites = []
368
- load_components(data["targets"], ProjectBody.Sprite, self.sprites)
369
- # Save origin of sprite in Sprite object:
370
- for sprite in self.sprites:
371
- sprite.projectBody = self
372
- # Load monitors:
373
- self.monitors = []
374
- load_components(data["monitors"], ProjectBody.Monitor, self.monitors)
375
- # Save origin of monitor in Monitor object:
376
- for monitor in self.monitors:
377
- monitor.projectBody = self
378
- # Set extensions and meta attributs:
379
- self.extensions = data["extensions"]
380
- self.meta = data["meta"]
381
- def to_json(self):
382
- """
383
- Returns a valid project JSON dict with the contents of this project
384
- """
385
- return_data = {}
386
- return_data["targets"] = [sprite.to_json() for sprite in self.sprites]
387
- return_data["monitors"] = [monitor.to_json() for monitor in self.monitors]
388
- return_data["extensions"] = self.extensions
389
- return_data["meta"] = self.meta
390
- return return_data
391
- # -- Functions to get info --
392
- def blocks(self):
393
- return [block for sprite in self.sprites for block in sprite.blocks]
394
- def block_count(self):
395
- return len(self.blocks())
396
- def assets(self):
397
- return [sound for sprite in self.sprites for sound in sprite.sounds] + [costume for sprite in self.sprites for
398
- costume in sprite.costumes]
399
- def asset_count(self):
400
- return len(self.assets())
401
- def variable_by_id(self, variable_id):
402
- for sprite in self.sprites:
403
- r = sprite.variable_by_id(variable_id)
404
- if r is not None:
405
- return r
406
- def list_by_id(self, list_id):
407
- for sprite in self.sprites:
408
- r = sprite.list_by_id(list_id)
409
- if r is not None:
410
- return r
411
- def sprite_by_name(self, sprite_name):
412
- matching = list(filter(lambda x: x.name == sprite_name, self.sprites))
413
- if matching == []:
414
- return None
415
- return matching[0]
416
- def user_agent(self):
417
- return self.meta["agent"]
418
- def save(self, *, filename=None, dir=""):
419
- """
420
- Saves the project body to the given directory.
421
- Args:
422
- filename (str): The name that will be given to the downloaded file.
423
- dir (str): The path of the directory the file will be saved in.
424
- """
425
- if not (dir.endswith("/") or dir.endswith("\\")):
426
- dir = dir + "/"
427
- if filename is None:
428
- filename = "project"
429
- filename = filename.replace(".sb3", "")
430
- with open(f"{dir}{filename}.sb3", "w") as d:
431
- json.dump(self.to_json(), d, indent=4)
432
- def get_empty_project_pb():
433
- pb = ProjectBody()
434
- pb.from_json(empty_project_json)
435
- return pb
436
- def get_pb_from_dict(project_json: dict):
437
- pb = ProjectBody()
438
- pb.from_json(project_json)
439
- return pb
440
- def _load_sb3_file(path_to_file):
441
- try:
442
- with open(path_to_file, "r") as r:
443
- return json.loads(r.read())
444
- except Exception as e:
445
- with zipfile.ZipFile(path_to_file, 'r') as zip_ref:
446
- # Check if the file exists in the zip
447
- if "project.json" in zip_ref.namelist():
448
- # Read the file as bytes
449
- with zip_ref.open("project.json") as file:
450
- return json.loads(file.read())
451
- else:
452
- raise ValueError("specified sb3 archive doesn't contain project.json")
453
- def read_sb3_file(path_to_file):
454
- pb = ProjectBody()
455
- pb.from_json(_load_sb3_file(path_to_file))
456
- return pb
457
- def download_asset(asset_id_with_file_ext, *, filename=None, dir=""):
458
- if not (dir.endswith("/") or dir.endswith("\\")):
459
- dir = dir + "/"
460
- try:
461
- if filename is None:
462
- filename = str(asset_id_with_file_ext)
463
- response = requests.get(
464
- "https://assets.scratch.mit.edu/" + str(asset_id_with_file_ext),
465
- timeout=10,
466
- )
467
- open(f"{dir}{filename}", "wb").write(response.content)
468
- except Exception:
469
- raise (
470
- exceptions.FetchError(
471
- "Failed to download asset"
472
- )
473
- )
474
-
475
- # The method for uploading an asset by id requires authentication and can be found in the site.session.Session class
File without changes
@@ -1,66 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC, abstractmethod
4
- from typing import TypeVar, Optional
5
-
6
- import requests
7
- from scratchattach.utils import exceptions, commons
8
- from . import session
9
-
10
- C = TypeVar("C", bound="BaseSiteComponent")
11
- class BaseSiteComponent(ABC):
12
- _session: Optional[session.Session]
13
- update_api: str
14
- _headers: dict[str, str]
15
- _cookies: dict[str, str]
16
-
17
- # @abstractmethod
18
- # def __init__(self): # dataclasses do not implement __init__ directly
19
- # pass
20
-
21
- def update(self):
22
- """
23
- Updates the attributes of the object by performing an API response. Returns True if the update was successful.
24
- """
25
- response = self.update_function(
26
- self.update_api,
27
- headers=self._headers,
28
- cookies=self._cookies, timeout=10
29
- )
30
- # Check for 429 error:
31
- # Note, this is a bit naïve
32
- if "429" in str(response):
33
- return "429"
34
-
35
- if response.text == '{\n "response": "Too many requests"\n}':
36
- return "429"
37
-
38
- # If no error: Parse JSON:
39
- response = response.json()
40
- if "code" in response:
41
- return False
42
-
43
- return self._update_from_dict(response)
44
-
45
- @abstractmethod
46
- def _update_from_dict(self, data) -> bool:
47
- """
48
- Parses the API response that is fetched in the update-method. Class specific, must be overridden in classes inheriting from this one.
49
- """
50
-
51
- def _assert_auth(self):
52
- if self._session is None:
53
- raise exceptions.Unauthenticated(
54
- "You need to use session.connect_xyz (NOT get_xyz) in order to perform this operation.")
55
-
56
- def _make_linked_object(self, identificator_id, identificator, Class: type[C], NotFoundException) -> C:
57
- """
58
- Internal function for making a linked object (authentication kept) based on an identificator (like a project id or username)
59
- Class must inherit from BaseSiteComponent
60
- """
61
- return commons._get_object(identificator_id, identificator, Class, NotFoundException, self._session)
62
-
63
- update_function = requests.get
64
- """
65
- Internal function run on update. Function is a method of the 'requests' module/class
66
- """