Fixed FS permission handler need to be asked twice (#2395)

* Fixed FS permission handler need to be asked twice

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Andrea Giammarchi
2025-10-23 16:14:21 +02:00
committed by GitHub
parent 8cd9c4c382
commit f8cf58d6c4
5 changed files with 32 additions and 10 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@pyscript/core",
"version": "0.7.6",
"version": "0.7.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pyscript/core",
"version": "0.7.6",
"version": "0.7.10",
"license": "APACHE-2.0",
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.7.6",
"version": "0.7.10",
"type": "module",
"description": "PyScript",
"module": "./index.js",

View File

@@ -7,7 +7,7 @@ export default {
"fetch.py": "import json,js\nfrom pyscript.util import as_bytearray\nclass _Response:\n\tdef __init__(A,response):A._response=response\n\tdef __getattr__(A,attr):return getattr(A._response,attr)\n\tasync def arrayBuffer(B):\n\t\tA=await B._response.arrayBuffer()\n\t\tif hasattr(A,'to_py'):return A.to_py()\n\t\treturn memoryview(as_bytearray(A))\n\tasync def blob(A):return await A._response.blob()\n\tasync def bytearray(A):B=await A._response.arrayBuffer();return as_bytearray(B)\n\tasync def json(A):return json.loads(await A.text())\n\tasync def text(A):return await A._response.text()\nclass _DirectResponse:\n\t@staticmethod\n\tdef setup(promise,response):A=promise;A._response=_Response(response);return A._response\n\tdef __init__(B,promise):A=promise;B._promise=A;A._response=None;A.arrayBuffer=B.arrayBuffer;A.blob=B.blob;A.bytearray=B.bytearray;A.json=B.json;A.text=B.text\n\tasync def _response(A):\n\t\tif not A._promise._response:await A._promise\n\t\treturn A._promise._response\n\tasync def arrayBuffer(A):B=await A._response();return await B.arrayBuffer()\n\tasync def blob(A):B=await A._response();return await B.blob()\n\tasync def bytearray(A):B=await A._response();return await B.bytearray()\n\tasync def json(A):B=await A._response();return await B.json()\n\tasync def text(A):B=await A._response();return await B.text()\ndef fetch(url,**B):C=js.JSON.parse(json.dumps(B));D=lambda response,*B:_DirectResponse.setup(A,response);A=js.fetch(url,C).then(D);_DirectResponse(A);return A",
"ffi.py": "try:\n\timport js;from pyodide.ffi import create_proxy as _cp,to_js as _py_tjs,jsnull;from_entries=js.Object.fromEntries;is_none=lambda value:value is None or value is jsnull\n\tdef _tjs(value,**A):\n\t\tB='dict_converter'\n\t\tif not hasattr(A,B):A[B]=from_entries\n\t\treturn _py_tjs(value,**A)\nexcept:from jsffi import create_proxy as _cp;from jsffi import to_js as _tjs;import js;jsnull=js.Object.getPrototypeOf(js.Object.prototype);is_none=lambda value:value is None or value is jsnull\ncreate_proxy=_cp\nto_js=_tjs\ntry:\n\tfrom polyscript import ffi as _ffi;direct=_ffi.direct;gather=_ffi.gather;query=_ffi.query\n\tdef assign(source,*B):\n\t\tA=source\n\t\tfor C in B:_ffi.assign(A,to_js(C))\n\t\treturn A\nexcept:\n\timport js;_assign=js.Object.assign;direct=lambda source:source\n\tdef assign(source,*B):\n\t\tA=source\n\t\tfor C in B:_assign(A,to_js(C))\n\t\treturn A",
"flatted.py": "import json as _json\nclass _Known:\n\tdef __init__(A):A.key=[];A.value=[]\nclass _String:\n\tdef __init__(A,value):A.value=value\ndef _array_keys(value):\n\tA=[];B=0\n\tfor C in value:A.append(B);B+=1\n\treturn A\ndef _object_keys(value):\n\tA=[]\n\tfor B in value:A.append(B)\n\treturn A\ndef _is_array(value):return isinstance(value,(list,tuple))\ndef _is_object(value):return isinstance(value,dict)\ndef _is_string(value):return isinstance(value,str)\ndef _index(known,input,value):B=value;A=known;input.append(B);C=str(len(input)-1);A.key.append(B);A.value.append(C);return C\ndef _loop(keys,input,known,output):\n\tA=output\n\tfor B in keys:\n\t\tC=A[B]\n\t\tif isinstance(C,_String):_ref(B,input[int(C.value)],input,known,A)\n\treturn A\ndef _ref(key,value,input,known,output):\n\tB=known;A=value\n\tif _is_array(A)and A not in B:B.append(A);A=_loop(_array_keys(A),input,B,A)\n\telif _is_object(A)and A not in B:B.append(A);A=_loop(_object_keys(A),input,B,A)\n\toutput[key]=A\ndef _relate(known,input,value):\n\tB=known;A=value\n\tif _is_string(A)or _is_array(A)or _is_object(A):\n\t\ttry:return B.value[B.key.index(A)]\n\t\texcept:return _index(B,input,A)\n\treturn A\ndef _transform(known,input,value):\n\tB=known;A=value\n\tif _is_array(A):\n\t\tC=[]\n\t\tfor F in A:C.append(_relate(B,input,F))\n\t\treturn C\n\tif _is_object(A):\n\t\tD={}\n\t\tfor E in A:D[E]=_relate(B,input,A[E])\n\t\treturn D\n\treturn A\ndef _wrap(value):\n\tA=value\n\tif _is_string(A):return _String(A)\n\tif _is_array(A):\n\t\tB=0\n\t\tfor D in A:A[B]=_wrap(D);B+=1\n\telif _is_object(A):\n\t\tfor C in A:A[C]=_wrap(A[C])\n\treturn A\ndef parse(value,*C,**D):\n\tA=value;E=_json.loads(A,*C,**D);B=[]\n\tfor A in E:B.append(_wrap(A))\n\tinput=[]\n\tfor A in B:\n\t\tif isinstance(A,_String):input.append(A.value)\n\t\telse:input.append(A)\n\tA=input[0]\n\tif _is_array(A):return _loop(_array_keys(A),input,[A],A)\n\tif _is_object(A):return _loop(_object_keys(A),input,[A],A)\n\treturn A\ndef stringify(value,*D,**E):\n\tB=_Known();input=[];C=[];A=int(_index(B,input,value))\n\twhile A<len(input):C.append(_transform(B,input,input[A]));A+=1\n\treturn _json.dumps(C,*D,**E)",
"fs.py": "_A='pyscript'\nmounted={}\nasync def mount(path,mode='readwrite',root='',id=_A):\n\tE=path;import js;from _pyscript import fs as A,interpreter as I;from pyscript.ffi import to_js as H;from pyscript.magic_js import RUNNING_IN_WORKER as J,sync;js.console.warn('experimental pyscript.fs ⚠️');B=None;C=f\"{E}@{id}\";F={'id':id,'mode':mode}\n\tif root!='':F['startIn']=root\n\tif J:\n\t\tG=sync.storeFSHandler(C,H(F))\n\t\tif isinstance(G,bool):D=G\n\t\telse:D=await G\n\t\tif D:from polyscript import IDBMap as K;L=K.new(A.NAMESPACE);B=await L.get(C)\n\t\telse:raise RuntimeError(A.ERROR)\n\telse:\n\t\tD=await A.idb.has(C)\n\t\tif D:B=await A.idb.get(C)\n\t\telse:B=await A.getFileSystemDirectoryHandle(H(F));await A.idb.set(C,B)\n\tmounted[E]=await I.mountNativeFS(E,B)\nasync def revoke(path,id=_A):\n\tfrom _pyscript import fs,interpreter as C;from pyscript.magic_js import RUNNING_IN_WORKER as D,sync;B=f\"{path}@{id}\"\n\tif D:A=sync.deleteFSHandler(B)\n\telse:\n\t\tA=await fs.idb.has(B)\n\t\tif A:A=await fs.idb.delete(B)\n\tif A:C._module.FS.unmount(path)\n\treturn A\nasync def sync(path):await mounted[path].syncfs()\nasync def unmount(path):from _pyscript import interpreter as A;await sync(path);A._module.FS.unmount(path)",
"fs.py": "_B='pyscript'\n_A=None\nmounted={}\nasync def get_handler(details):A=details;B=A.handler;C=A.options;D=await B.queryPermission(C);return B if D=='granted'else _A\nasync def mount(path,mode='readwrite',root='',id=_B):\n\tF=path;import js;from _pyscript import fs as C,interpreter as L;from pyscript.ffi import to_js as G;from pyscript.magic_js import RUNNING_IN_WORKER as M,sync as J;js.console.warn('experimental pyscript.fs ⚠️');A=_A;B=_A;D=f\"{F}@{id}\";H={'id':id,'mode':mode}\n\tif root!='':H['startIn']=root\n\tif M:\n\t\tI=J.storeFSHandler(D,G(H))\n\t\tif isinstance(I,bool):E=I\n\t\telse:E=await I\n\t\tif E:\n\t\t\tfrom polyscript import IDBMap as N;from pyscript import window;O=N.new(C.NAMESPACE);A=await O.get(D);B=await get_handler(A)\n\t\t\tif B is _A:await js.Promise.resolve(J.getFSHandler(A.options));B=A.handler\n\t\telse:raise RuntimeError(C.ERROR)\n\telse:\n\t\tE=await C.idb.has(D)\n\t\tif E:\n\t\t\tA=await C.idb.get(D);B=await get_handler(A)\n\t\t\tif B is _A:B=await C.getFileSystemDirectoryHandle(A.options)\n\t\telse:K=G(H);B=await C.getFileSystemDirectoryHandle(K);A={'handler':B,'options':K};await C.idb.set(D,G(A))\n\tmounted[F]=await L.mountNativeFS(F,B)\nasync def revoke(path,id=_B):\n\tfrom _pyscript import fs,interpreter as C;from pyscript.magic_js import RUNNING_IN_WORKER as D,sync;B=f\"{path}@{id}\"\n\tif D:A=sync.deleteFSHandler(B)\n\telse:\n\t\tA=await fs.idb.has(B)\n\t\tif A:A=await fs.idb.delete(B)\n\tif A:C._module.FS.unmount(path)\n\treturn A\nasync def sync(path):await mounted[path].syncfs()\nasync def unmount(path):from _pyscript import interpreter as A;await sync(path);A._module.FS.unmount(path)",
"magic_js.py": "import json,sys,js as globalThis\nfrom polyscript import config as _config,js_modules\nfrom pyscript.util import NotSupported\nRUNNING_IN_WORKER=not hasattr(globalThis,'document')\nconfig=json.loads(globalThis.JSON.stringify(_config))\nif'MicroPython'in sys.version:config['type']='mpy'\nelse:config['type']='py'\nclass JSModule:\n\tdef __init__(A,name):A.name=name\n\tdef __getattr__(B,field):\n\t\tA=field\n\t\tif not A.startswith('_'):return getattr(getattr(js_modules,B.name),A)\nfor name in globalThis.Reflect.ownKeys(js_modules):sys.modules[f\"pyscript.js_modules.{name}\"]=JSModule(name)\nsys.modules['pyscript.js_modules']=js_modules\nif RUNNING_IN_WORKER:\n\timport polyscript;PyWorker=NotSupported('pyscript.PyWorker','pyscript.PyWorker works only when running in the main thread')\n\ttry:import js;window=polyscript.xworker.window;document=window.document;js.document=document;js_import=window.Function('return (...urls) => Promise.all(urls.map((url) => import(url)))')()\n\texcept:message='Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer';globalThis.console.warn(message);window=NotSupported('pyscript.window',message);document=NotSupported('pyscript.document',message);js_import=None\n\tsync=polyscript.xworker.sync\n\tdef current_target():return polyscript.target\nelse:\n\timport _pyscript;from _pyscript import PyWorker as _PyWorker,js_import;from pyscript.ffi import to_js\n\tdef PyWorker(url,**A):return _PyWorker(url,to_js(A))\n\twindow=globalThis;document=globalThis.document;sync=NotSupported('pyscript.sync','pyscript.sync works only when running in a worker')\n\tdef current_target():return _pyscript.target",
"media.py": "from pyscript import window\nfrom pyscript.ffi import to_js\nclass Device:\n\tdef __init__(A,device):A._dom_element=device\n\t@property\n\tdef id(self):return self._dom_element.deviceId\n\t@property\n\tdef group(self):return self._dom_element.groupId\n\t@property\n\tdef kind(self):return self._dom_element.kind\n\t@property\n\tdef label(self):return self._dom_element.label\n\tdef __getitem__(A,key):return getattr(A,key)\n\t@classmethod\n\tasync def load(E,audio=False,video=True):\n\t\tC='video';B=video;A={};A['audio']=audio\n\t\tif isinstance(B,bool):A[C]=B\n\t\telse:\n\t\t\tA[C]={}\n\t\t\tfor D in B:A[C][D]=B[D]\n\t\treturn await window.navigator.mediaDevices.getUserMedia(to_js(A))\n\tasync def get_stream(A):B=A.kind.replace('input','').replace('output','');C={B:{'deviceId':{'exact':A.id}}};return await A.load(**C)\nasync def list_devices():return[Device(A)for A in await window.navigator.mediaDevices.enumerateDevices()]",
"storage.py": "_C='memoryview'\n_B='bytearray'\n_A='generic'\nfrom polyscript import storage as _storage\nfrom pyscript.flatted import parse as _parse\nfrom pyscript.flatted import stringify as _stringify\nfrom pyscript.ffi import is_none\ndef _to_idb(value):\n\tA=value\n\tif is_none(A):return _stringify(['null',0])\n\tif isinstance(A,(bool,float,int,str,list,dict,tuple)):return _stringify([_A,A])\n\tif isinstance(A,bytearray):return _stringify([_B,list(A)])\n\tif isinstance(A,memoryview):return _stringify([_C,list(A)])\n\tB=f\"Unexpected value: {A}\";raise TypeError(B)\ndef _from_idb(value):\n\tC=value;A,B=_parse(C)\n\tif A=='null':return\n\tif A==_A:return B\n\tif A==_B:return bytearray(B)\n\tif A==_C:return memoryview(bytearray(B))\n\treturn C\nclass Storage(dict):\n\tdef __init__(B,store):A=store;super().__init__({A:_from_idb(B)for(A,B)in A.entries()});B.__store__=A\n\tdef __delitem__(A,attr):A.__store__.delete(attr);super().__delitem__(attr)\n\tdef __setitem__(B,attr,value):A=value;B.__store__.set(attr,_to_idb(A));super().__setitem__(attr,A)\n\tdef clear(A):A.__store__.clear();super().clear()\n\tasync def sync(A):await A.__store__.sync()\nasync def storage(name='',storage_class=Storage):\n\tif not name:A='The storage name must be defined';raise ValueError(A)\n\treturn storage_class(await _storage(f\"@pyscript/{name}\"))",

View File

@@ -1,6 +1,13 @@
mounted = {}
async def get_handler(details):
handler = details.handler
options = details.options
permission = await handler.queryPermission(options)
return handler if permission == "granted" else None
async def mount(path, mode="readwrite", root="", id="pyscript"):
import js
from _pyscript import fs, interpreter
@@ -12,6 +19,7 @@ async def mount(path, mode="readwrite", root="", id="pyscript"):
js.console.warn("experimental pyscript.fs ⚠️")
details = None
handler = None
uid = f"{path}@{id}"
@@ -31,9 +39,16 @@ async def mount(path, mode="readwrite", root="", id="pyscript"):
if success:
from polyscript import IDBMap
from pyscript import window
idbm = IDBMap.new(fs.NAMESPACE)
details = await idbm.get(uid)
handler = await get_handler(details)
if handler is None:
# force await in either async or sync scenario
await js.Promise.resolve(sync.getFSHandler(details.options))
handler = details.handler
idb = IDBMap.new(fs.NAMESPACE)
handler = await idb.get(uid)
else:
raise RuntimeError(fs.ERROR)
@@ -41,10 +56,15 @@ async def mount(path, mode="readwrite", root="", id="pyscript"):
success = await fs.idb.has(uid)
if success:
handler = await fs.idb.get(uid)
details = await fs.idb.get(uid)
handler = await get_handler(details)
if handler is None:
handler = await fs.getFileSystemDirectoryHandle(details.options)
else:
handler = await fs.getFileSystemDirectoryHandle(to_js(options))
await fs.idb.set(uid, handler)
js_options = to_js(options)
handler = await fs.getFileSystemDirectoryHandle(js_options)
details = {"handler": handler, "options": js_options}
await fs.idb.set(uid, to_js(details))
mounted[path] = await interpreter.mountNativeFS(path, handler)

View File

@@ -12,6 +12,8 @@ export default {
return new Promise(($) => setTimeout($, seconds * 1000));
},
getFSHandler: (options) => getFileSystemDirectoryHandle(options),
/**
* Ask a user action via dialog and returns the directory handler once granted.
* @param {string} uid
@@ -22,7 +24,7 @@ export default {
if (await idb.has(uid)) return true;
return getFileSystemDirectoryHandle(options).then(
async (handler) => {
await idb.set(uid, handler);
await idb.set(uid, { handler, options });
return true;
},
() => false,