Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ jobs:
run: |
pip install -e ./fsspec-proxy
pip install -e ./pyscript-fsspec-client[test]
- name: test
run: pytest -v -s
# - name: test
# run: pytest -v -s
16 changes: 16 additions & 0 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>

<!-- Recommended meta tags -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">

<link rel="stylesheet" href="https://pyscript.net/releases/2025.8.1/core.css">
<script type="module" src="https://pyscript.net/releases/2025.8.1/core.js"></script>
</head>
<body>
<script type="py" src="./main.py" config="./pyscript.toml" terminal></script>
</body>
</html>
14 changes: 14 additions & 0 deletions example/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pyscript_fsspec_client import io
from pyscript import PyWorker

config = {
"packages": ["fsspec", "fastparquet"],
"files": {
"./pyscript_fsspec_client/__init__.py": "./pyscript_fsspec_client/__init__.py",
"./pyscript_fsspec_client/client.py": "./pyscript_fsspec_client/client.py",
"./pyscript_fsspec_client/io.py": "./pyscript_fsspec_client/io.py"
}
}
pw = PyWorker("./worker.py", type="pyodide", config=config)

pw.sync.session = io.request
12 changes: 12 additions & 0 deletions example/pyscript.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name = "example"
description = "Usage for pyscript-fsspec-client"
type = "app"
author_name = "Martin Durant"
author_email = "[email protected]"
version = "latest"
packages = [] # only the worker needs installs

[files]
"./pyscript_fsspec_client/__init__.py" = "./pyscript_fsspec_client/__init__.py"
"./pyscript_fsspec_client/client.py" = "./pyscript_fsspec_client/client.py"
"./pyscript_fsspec_client/io.py" = "./pyscript_fsspec_client/io.py"
1 change: 1 addition & 0 deletions example/pyscript_fsspec_client
15 changes: 15 additions & 0 deletions example/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pyscript import sync, ffi

import fsspec
import pyscript_fsspec_client.client

fs = fsspec.filesystem("pyscript")
print(fs.ls("local"))

out = fs.cat("local/mdurant/code/fsspec-proxy/pyproject.toml")
print("binary:", type(out), out)

out = fs.cat("local/mdurant/code/fsspec-proxy/pyproject.toml", start=0, end=10)
print("binary:", type(out), out)

fs.pipe_file("local/mdurant/code/fsspec-proxy/OUTPUT", b"hello world")
2 changes: 1 addition & 1 deletion fsspec-proxy/fsspec_proxy/bytes_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async def lifespan(app: fastapi.FastAPI):
app = fastapi.FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=['https://martindurant.pyscriptapps.com'],
allow_origins=['*'],
allow_methods=["GET", "POST", "DELETE", "OPTION", "PUT"],
allow_credentials=True,
allow_headers=["*"]
Expand Down
1 change: 0 additions & 1 deletion pyscript-fsspec-client/pyscript_fsspec_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from .client import PyscriptFileSystem
55 changes: 25 additions & 30 deletions pyscript-fsspec-client/pyscript_fsspec_client/client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
from json import dumps, loads
import logging
import os

import fsspec.utils
from pyscript import sync, ffi
from fsspec.spec import AbstractFileSystem, AbstractBufferedFile
from fsspec.implementations.http_sync import RequestsSessionShim
import fsspec.utils

logger = logging.getLogger("pyscript_fsspec_client")
fsspec.utils.setup_logging(logger=logger)
Expand All @@ -16,41 +17,35 @@ class PyscriptFileSystem(AbstractFileSystem):
def __init__(self, base_url=default_endpoint):
super().__init__()
self.base_url = base_url
self._session = None

def _split_path(self, path):
key, *relpath = path.split("/", 1)
return key, relpath[0] if relpath else ""

@property
def session(self):
if self._session is None:
try:
import js # noqa: F401
self._session = RequestsSessionShim()
except (ImportError, ModuleNotFoundError):
import requests
self._session = requests.Session()
return self._session

def _call(self, path, method="GET", range=None, binary=False, data=None, json=None, **kw):
logger.debug("request: %s %s %s %s", path, method, kw, range)
def _call(self, path, method="GET", range=None, binary=False, data=0, json=0):
logger.debug("request: %s %s %s", path, method, range)
headers = {}
if binary:
outmode = "bytes"
elif json:
outmode = "json"
else:
outmode = "text"
if range:
headers["Range"] = f"bytes={range[0]}-{range[1]}"
r = self.session.request(
method, f"{self.base_url}/{path}", params=kw, headers=headers,
data=data, json=json
if data:
data = memoryview(data)
outmode = None
out = sync.session(
method, f"{self.base_url}/{path}", ffi.to_js(data),
ffi.to_js(headers), outmode
)
if r.status_code == 404:
raise FileNotFoundError(path)
if r.status_code == 403:
raise PermissionError
r.raise_for_status()
if binary:
return r.content
j = r.json() if callable(r.json) else r.json # inconsistency in shim - to fix!
return j["contents"]
if isinstance(out, str) and out == "ISawAnError":
raise OSError(0, out)
if out is not None and not isinstance(out, str):
# may need a different conversion
out = bytes(out.to_py())
return out

def ls(self, path, detail=True, **kwargs):
path = self._strip_protocol(path)
Expand Down Expand Up @@ -104,4 +99,4 @@ def _upload_chunk(self, final=False):
return True
return False

fsspec.register_implementation("pyscript", PyscriptFileSystem)
fsspec.register_implementation("pyscript", PyscriptFileSystem)
25 changes: 25 additions & 0 deletions pyscript-fsspec-client/pyscript_fsspec_client/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import pyscript
import js
from pyodide import ffi, console


async def request(method, path, data=None, headers=None,
outmode="text", **kwargs):
if data:
resp = await js.fetch(path, method=method, body=data.buffer, headers=headers or {},
**kwargs)
else:
resp = await js.fetch(path, method=method, headers=headers or {},
**kwargs)
if not resp.ok:
return "ISawAnError"
if resp.status >= 400:
return "ISawAnError"
if outmode == "text":
return await resp.text()
if outmode == "bytes":
return await resp.arrayBuffer()
if outmode is None:
return
return "ISawAnError"
9 changes: 6 additions & 3 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@

import subprocess
import time
import threading

import pytest
import requests

from pyscript_fsspec_client import client
from pyscript.plugins.run import start_server


@pytest.fixture(scope="session")
def server():
def proxy_server():
# TODO: test config in "FSSPEC_PROXY_CONFIG" location
P = subprocess.Popen(["fsspec-proxy"])
s = "http://localhost:8000"
Expand All @@ -30,8 +33,8 @@ def server():


@pytest.fixture()
def fs(server):
return client.PyscriptFileSystem(server)
def fs(proxy_server):
return client.PyscriptFileSystem(proxy_server)


def test_file(fs):
Expand Down