scratchattach 3.0.0b0__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.
- cli/__about__.py +1 -0
- cli/__init__.py +26 -0
- cli/cmd/__init__.py +4 -0
- cli/cmd/group.py +127 -0
- cli/cmd/login.py +60 -0
- cli/cmd/profile.py +7 -0
- cli/cmd/sessions.py +5 -0
- cli/context.py +142 -0
- cli/db.py +66 -0
- cli/namespace.py +14 -0
- cloud/__init__.py +2 -0
- cloud/_base.py +483 -0
- cloud/cloud.py +183 -0
- editor/__init__.py +22 -0
- editor/asset.py +265 -0
- editor/backpack_json.py +115 -0
- editor/base.py +191 -0
- editor/block.py +584 -0
- editor/blockshape.py +357 -0
- editor/build_defaulting.py +51 -0
- editor/code_translation/__init__.py +0 -0
- editor/code_translation/parse.py +177 -0
- editor/comment.py +80 -0
- editor/commons.py +145 -0
- editor/extension.py +50 -0
- editor/field.py +99 -0
- editor/inputs.py +138 -0
- editor/meta.py +117 -0
- editor/monitor.py +185 -0
- editor/mutation.py +381 -0
- editor/pallete.py +88 -0
- editor/prim.py +174 -0
- editor/project.py +381 -0
- editor/sprite.py +609 -0
- editor/twconfig.py +114 -0
- editor/vlb.py +134 -0
- eventhandlers/__init__.py +0 -0
- eventhandlers/_base.py +101 -0
- eventhandlers/cloud_events.py +130 -0
- eventhandlers/cloud_recorder.py +26 -0
- eventhandlers/cloud_requests.py +544 -0
- eventhandlers/cloud_server.py +249 -0
- eventhandlers/cloud_storage.py +135 -0
- eventhandlers/combine.py +30 -0
- eventhandlers/filterbot.py +163 -0
- eventhandlers/message_events.py +42 -0
- other/__init__.py +0 -0
- other/other_apis.py +598 -0
- other/project_json_capabilities.py +475 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +1 -1
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- site/__init__.py +0 -0
- site/_base.py +93 -0
- site/activity.py +426 -0
- site/alert.py +226 -0
- site/backpack_asset.py +119 -0
- site/browser_cookie3_stub.py +17 -0
- site/browser_cookies.py +61 -0
- site/classroom.py +454 -0
- site/cloud_activity.py +121 -0
- site/comment.py +228 -0
- site/forum.py +436 -0
- site/placeholder.py +132 -0
- site/project.py +932 -0
- site/session.py +1323 -0
- site/studio.py +704 -0
- site/typed_dicts.py +151 -0
- site/user.py +1252 -0
- utils/__init__.py +0 -0
- utils/commons.py +263 -0
- utils/encoder.py +161 -0
- utils/enums.py +237 -0
- utils/exceptions.py +277 -0
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -37
- scratchattach/__main__.py +0 -93
- scratchattach-3.0.0b0.dist-info/RECORD +0 -8
- scratchattach-3.0.0b0.dist-info/top_level.txt +0 -1
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +0 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,475 @@
|
|
|
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
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
cli/__about__.py,sha256=rWWgBXhOQakEGEip4jZgZsmGHkIU08m01PRgPUfmHdg,17
|
|
2
|
+
cli/__init__.py,sha256=WAJmqnZi5RoJGlx3fngQAi9APNf84QlicncbsPRwhkg,700
|
|
3
|
+
cli/context.py,sha256=7m9i-J2j36MN8uPJm8ydjMFKWZjJQAyu5NBluL8eQUE,4950
|
|
4
|
+
cli/db.py,sha256=sV-AwyYc8WeHalpadAV1vD0HXkEmZrcvhLGJA0q0TD0,1920
|
|
5
|
+
cli/namespace.py,sha256=qWH1QI9ot-5Y1ahpY9JHw8HTGnlFwHRKLoKxvxFqk5A,445
|
|
6
|
+
cli/cmd/__init__.py,sha256=KiNP3GVwCf-NE_yzYgDvza3hR3Sr1B0-wqu5Xaxi_g4,110
|
|
7
|
+
cli/cmd/group.py,sha256=j8jqy2N4KEChe6cUus5MczoqGKDzjOIgG2mBmxVmb-A,4144
|
|
8
|
+
cli/cmd/login.py,sha256=BnPUCUDG58gNly76OZdBT22oxaZY50b_VHnQWx2p40I,1914
|
|
9
|
+
cli/cmd/profile.py,sha256=jT-JoJhFCtzbyskdQpi8_ckVzrVqDghlh-zEEEchFDI,172
|
|
10
|
+
cli/cmd/sessions.py,sha256=JzBx6BTjddqU-UGZz4CtYpBKb9CC7lbpXLk19Pz7Zoo,116
|
|
11
|
+
cloud/__init__.py,sha256=MN7s4grQcciqUO7TiFjTbU2sC69m6XXlwOHNRbN3FKo,41
|
|
12
|
+
cloud/_base.py,sha256=tXVWPsL9Ybvu2H0zQ5R9GexgeHAarEv4GS16foD3_qw,19251
|
|
13
|
+
cloud/cloud.py,sha256=zq0Z-N2EF2uUxpJ0uZlITY2DeKkjb3TxwK_etci-xCM,8470
|
|
14
|
+
editor/__init__.py,sha256=pdq-dg7fa4cj6eksu5yzUe27DGY6CLKVUtCSrbYCsM8,807
|
|
15
|
+
editor/asset.py,sha256=Ut_Em2rzlzswi2aDTvyRZWvF5JKLY8IAmoLMOQLBpjQ,8148
|
|
16
|
+
editor/backpack_json.py,sha256=zhNRKDmlA9Oz-IixjLRnSwVG6JfKURs31KhQPjJu_bs,3880
|
|
17
|
+
editor/base.py,sha256=MUQ4zqw-IaFZDH9o_F9k7F3mptymR67E8ITkaMfnD5M,5311
|
|
18
|
+
editor/block.py,sha256=2UKKQbrDdsI6dbswo2swvIr7jXiXA114TCtXDO9DSFw,19085
|
|
19
|
+
editor/blockshape.py,sha256=Q8CAWsBc2O5VqcGhUxbJoW1RRfy7kBxz1AporkExBi8,25318
|
|
20
|
+
editor/build_defaulting.py,sha256=rRDSXpIugWENYs6X50A_a3BJfWJuzEtwW1bv9IAyo-M,1421
|
|
21
|
+
editor/comment.py,sha256=G_eFxeaC_vKbCFpDfw4oZxIReUBoBUaCA5q35O-AW6Y,2403
|
|
22
|
+
editor/commons.py,sha256=0mm30KjAenl_V2BXbkdirFN62SraYfGUariXB-TXNlU,3583
|
|
23
|
+
editor/extension.py,sha256=PFVl5SrS66M4Md98I_dsfeL9BLeWwvyWezmhnTRseIQ,1541
|
|
24
|
+
editor/field.py,sha256=_AmBwrb5TeG_CRm312I69T2xXlzuKYeudpygPyA4698,2994
|
|
25
|
+
editor/inputs.py,sha256=bmCLsxgGimkESbKEnhsnw_ZSRpBDsWRIcEoYPFMx9Zs,4505
|
|
26
|
+
editor/meta.py,sha256=QCHk5DKvYAw7F5Gg4OIJNcB7PhK8u5YgfDmGQPWobTs,3330
|
|
27
|
+
editor/monitor.py,sha256=A30XadTjw_G0KsTnwo3YEI31nkplc1DnO8AirjmB3zc,5921
|
|
28
|
+
editor/mutation.py,sha256=o2msH-s6UxFZ-LuYF1BlR4CBm-ulKTVpStux54cxnDk,12246
|
|
29
|
+
editor/pallete.py,sha256=OY76grzJLUWsAKi2x-mipEXv5f-9I-s9UjoLZ3FmtBM,2299
|
|
30
|
+
editor/prim.py,sha256=v74mkdLwsXAKPy_KPesugv-xCxA6YcGU6_v7Zvc2YEY,5919
|
|
31
|
+
editor/project.py,sha256=tWf9kULlJjMCV1t5AofIDFqGylBYQ31vpjDMJ3jM3_Q,12884
|
|
32
|
+
editor/sprite.py,sha256=M_rrkErJswH9stKvrywrkaoNEwgRBbF7-Si17W-0vTs,21205
|
|
33
|
+
editor/twconfig.py,sha256=iE6ylAsZzniAfhL09GkZSFn1XacYtCQPzRCUSPIBzDA,3324
|
|
34
|
+
editor/vlb.py,sha256=Fl2gGwZyYh54uOhQ7XITfCgCpJTQB2P8wy47PKY3Qyk,4151
|
|
35
|
+
editor/code_translation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
editor/code_translation/parse.py,sha256=FjuHVg_tNzkGcGSNwgH6MzAHcf5YCvaEUSYukyJkwbk,4932
|
|
37
|
+
eventhandlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
eventhandlers/_base.py,sha256=97vFbuhSoJeYb-jrEWDdbZWQ7e_54CY3ktrq8d_-w8g,3142
|
|
39
|
+
eventhandlers/cloud_events.py,sha256=LDENLVopVwKDVrcfkR1spKzT45ET-EKqBdtCzXoaS5g,5228
|
|
40
|
+
eventhandlers/cloud_recorder.py,sha256=dvob4-aKLxE9WFqvCNv28M_r3r3OEkET6rwmu5yl4jk,795
|
|
41
|
+
eventhandlers/cloud_requests.py,sha256=CQsA6wzW6C0xJYtw6g0X8x5DY5EHkDE1tAOMVigzhzA,25113
|
|
42
|
+
eventhandlers/cloud_server.py,sha256=j2Iiuxsp9xOLeZ6S7iT7Yto8u599O57kRUZLMMIxjQ0,12431
|
|
43
|
+
eventhandlers/cloud_storage.py,sha256=YYcvRjhIuOboWVemMKFEtUhrd6UeUFO_BlbAIH7oaeQ,4609
|
|
44
|
+
eventhandlers/combine.py,sha256=YiWI6WI1BySioXpfYaMv8noBM14EjZa7dtsJsMTshEU,894
|
|
45
|
+
eventhandlers/filterbot.py,sha256=V5dQErz_yFpSioh5VxUDaBW8L9ny1uMviTt7x-KFC1k,7612
|
|
46
|
+
eventhandlers/message_events.py,sha256=KvznXAeNGk1WWCxd7PI95yovX-58TyCBNDdXbrYgb8Q,1776
|
|
47
|
+
other/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
other/other_apis.py,sha256=yZ06rCZmJLSZZdfv1V_VgRBq5dUxS31YHkpsdOxAezw,17509
|
|
49
|
+
other/project_json_capabilities.py,sha256=07t1iMgWm4Qd06jHyQ3vK7tROguvc2RQCo78enrdSlA,22646
|
|
50
|
+
scratchattach-3.0.0b1.dist-info/licenses/LICENSE,sha256=1PRKLhZU4wYt5M-C9f7q0W3go3u_ojnZMNOdR3g3J-E,1080
|
|
51
|
+
site/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
+
site/_base.py,sha256=D2SOp2aFiDP0hG7-xP8kK1pmoAz5TOi7_iWt6Ulpsbg,3283
|
|
53
|
+
site/activity.py,sha256=g5fg97xHUAKOkaZ4VzKl3hVpICZhv1mpDCO6LlmkoTU,17871
|
|
54
|
+
site/alert.py,sha256=V6asmcWy4tcQgWhG95rpqXP0KeUy7VQU9wD9AMhCqds,9324
|
|
55
|
+
site/backpack_asset.py,sha256=__VZomGDJkbgWj2ridQQArMMLWoSMv8dpO7PPpvPgBU,3322
|
|
56
|
+
site/browser_cookie3_stub.py,sha256=codk0knOP5C0YThaRazvqsqX7X7WnrD2UwFd1nFG7mg,1422
|
|
57
|
+
site/browser_cookies.py,sha256=uQyjJJ4HCu46R4tPWCcTUqDMXSXhQ4KQUobqCSxScSA,1864
|
|
58
|
+
site/classroom.py,sha256=lpHh2OkHeFzGUaFbf9PWIrosYOBo2cRrfc6fsex2w44,18139
|
|
59
|
+
site/cloud_activity.py,sha256=vMQy2k3jzPbOa3_TiH23B9dTk4BQA_z0q_Ab6TFkeV8,5397
|
|
60
|
+
site/comment.py,sha256=kUZxbjChs8K66vShz-Q1y1s72yJ44hFXJUYLsSb9rUs,9449
|
|
61
|
+
site/forum.py,sha256=-XLi3UJOwDt0Ye8CqP4c-sSxOkPSe5fPVa9b7MOm64k,16553
|
|
62
|
+
site/placeholder.py,sha256=BTOroGKA3lpgKPJXFeveEuMdcNkOIddpbNTrV-48s-o,5446
|
|
63
|
+
site/project.py,sha256=Zc5TN4PEGC1fTze_rkU7_-Y1YDuSXrkHaJGD3eeLgqU,34750
|
|
64
|
+
site/session.py,sha256=kd4IKaMXmtWGJJnFV3wOoLAF2S_012zHkdGcMZgUFxs,54451
|
|
65
|
+
site/studio.py,sha256=sAqFJDjhxf-9NynLB7mrCgzxq8td1oqa1zTW9MP1yTM,26195
|
|
66
|
+
site/typed_dicts.py,sha256=Hq65u56OSLdcoQ0F0A_CxZ8WBY5d_0VuJmUEMd8KmpU,3611
|
|
67
|
+
site/user.py,sha256=P2nsSGo-Z1QlXzAQxstOOq-E4QGN9HVA4dIQaJd1YFY,47404
|
|
68
|
+
utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
utils/commons.py,sha256=DpBTxRC2XeKURW5YJwAT6teAfYG7221GxClTaZ0-yQE,8465
|
|
70
|
+
utils/encoder.py,sha256=1lOXBbqo2JGMdns6Q5WGhAB9p7Q8trduBk-SZgmcEKw,2559
|
|
71
|
+
utils/enums.py,sha256=5HhXidczUYYVwHf6jfJcMA80jK4V0imaltLEu5epIF8,11127
|
|
72
|
+
utils/exceptions.py,sha256=T1nBbbkTZVkZU7uNaqBqSbNTGtOaOnTVLRRqUTatTis,7556
|
|
73
|
+
utils/optional_async.py,sha256=zTCFt6tpSvlcwns1RgAACKplA1SSFyVWAcDRS8kLLjE,4721
|
|
74
|
+
utils/requests.py,sha256=Brl94PCyblaQopanXyyQZ8ZoaWuFrK3NqUTZWW22gpY,9608
|
|
75
|
+
scratchattach-3.0.0b1.dist-info/METADATA,sha256=JnNVUZxbXAuGUH0mn9nE4Cuu17ko-wNNy1OLdPMPSWU,5633
|
|
76
|
+
scratchattach-3.0.0b1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
77
|
+
scratchattach-3.0.0b1.dist-info/entry_points.txt,sha256=vNXuP05TQKEoIzmzmUzS7zbtSZx0p3JmeUW3QhNdYfg,56
|
|
78
|
+
scratchattach-3.0.0b1.dist-info/top_level.txt,sha256=PFfH9Sb4fMOY99H0Xiuuc8nEGjAT_-anhTaORDZBd7A,48
|
|
79
|
+
scratchattach-3.0.0b1.dist-info/RECORD,,
|
site/__init__.py
ADDED
|
File without changes
|
site/_base.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TypeVar, Optional, Self, Union, Any, Generic
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from scratchattach.utils import exceptions, commons, optional_async
|
|
10
|
+
from scratchattach.utils import requests as m_requests
|
|
11
|
+
from . import session
|
|
12
|
+
|
|
13
|
+
D = TypeVar("D")
|
|
14
|
+
C = TypeVar("C", bound="BaseSiteComponent")
|
|
15
|
+
class BaseSiteComponent(ABC, Generic[D]):
|
|
16
|
+
_session: Optional[session.Session]
|
|
17
|
+
update_api: str
|
|
18
|
+
_headers: dict[str, str]
|
|
19
|
+
_cookies: dict[str, str]
|
|
20
|
+
oa_http_session: Optional[m_requests.OAHTTPSession] = None
|
|
21
|
+
|
|
22
|
+
# @abstractmethod
|
|
23
|
+
# def __init__(self): # dataclasses do not implement __init__ directly
|
|
24
|
+
# pass
|
|
25
|
+
|
|
26
|
+
def update(self):
|
|
27
|
+
"""
|
|
28
|
+
Updates the attributes of the object by performing an API response. Returns True if the update was successful.
|
|
29
|
+
"""
|
|
30
|
+
response = self.update_function(
|
|
31
|
+
self.update_api,
|
|
32
|
+
headers=self._headers,
|
|
33
|
+
cookies=self._cookies, timeout=10
|
|
34
|
+
)
|
|
35
|
+
# Check for 429 error:
|
|
36
|
+
# Note, this is a bit naïve
|
|
37
|
+
if "429" in str(response):
|
|
38
|
+
return "429"
|
|
39
|
+
|
|
40
|
+
if json.loads(response.text) == {"response": "Too many requests"}:
|
|
41
|
+
return "429"
|
|
42
|
+
|
|
43
|
+
# If no error: Parse JSON:
|
|
44
|
+
response = response.json()
|
|
45
|
+
if "code" in response:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
return self._update_from_dict(response)
|
|
49
|
+
|
|
50
|
+
def updated(self) -> Self:
|
|
51
|
+
self.update()
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def _update_from_dict(self, data: D) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Parses the API response that is fetched in the update-method. Class specific, must be overridden in classes inheriting from this one.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def _assert_auth(self):
|
|
61
|
+
if self._session is None:
|
|
62
|
+
raise exceptions.Unauthenticated(
|
|
63
|
+
"You need to use session.connect_xyz (NOT get_xyz) in order to perform this operation.")
|
|
64
|
+
|
|
65
|
+
def _make_linked_object(self, identificator_id, identificator, Class: type[C], NotFoundException) -> C:
|
|
66
|
+
"""
|
|
67
|
+
Internal function for making a linked object (authentication kept) based on an identificator (like a project id or username)
|
|
68
|
+
Class must inherit from BaseSiteComponent
|
|
69
|
+
"""
|
|
70
|
+
return commons._get_object(identificator_id, identificator, Class, NotFoundException, self._session)
|
|
71
|
+
|
|
72
|
+
def supply_data_dict(self, data: D) -> bool:
|
|
73
|
+
return self._update_from_dict(data)
|
|
74
|
+
|
|
75
|
+
update_function = requests.get
|
|
76
|
+
"""
|
|
77
|
+
Internal function run on update. Function is a method of the 'requests' module/class
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def _make_request(
|
|
81
|
+
self,
|
|
82
|
+
method: Union[m_requests.HTTPMethod, str],
|
|
83
|
+
url: str,
|
|
84
|
+
*,
|
|
85
|
+
cookies: Optional[dict[str, str]] = None,
|
|
86
|
+
headers: Optional[dict[str, str]] = None,
|
|
87
|
+
params: Optional[dict[str, str]] = None,
|
|
88
|
+
data: Optional[Union[dict[str, str], str]] = None,
|
|
89
|
+
json: Optional[Any] = None
|
|
90
|
+
) -> optional_async.CARequest:
|
|
91
|
+
if self.oa_http_session is None:
|
|
92
|
+
raise ValueError("This BaseSiteComponent has no oa_http_session.")
|
|
93
|
+
return self.oa_http_session.request(method, url, cookies=cookies, headers=headers, params=params, data=data, json=json)
|