Skip to content
Open
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
3 changes: 2 additions & 1 deletion viur/scriptor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .requests import WebRequest, WebResponse
from .dialog import Dialog
from .file import File
from .file import File, ZipFile
from .logger import logger
from .module import Modules
from ._utils import is_pyodide_context, is_pyodide_in_browser, gather_async_iterator, clear_console
Expand Down Expand Up @@ -69,6 +69,7 @@ async def _init_modules(base_url=None, username=None, password=None, login_skey=
'map_extract_items',
'ProgressBar',
'version',
"ZipFile",
]

if is_pyodide_context():
Expand Down
158 changes: 103 additions & 55 deletions viur/scriptor/file.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,87 @@
import json
import zipfile
import chardet
import magic
import typing as t
from openpyxl.reader.excel import ExcelReader
from io import BytesIO, StringIO
import csv
from ._utils import list_table_to_dict_table, normalize_table, list_to_excel, list_to_csv, save_file
from .dialog import Dialog


class File:
class FileBase:
def __init__(self, data: bytes | BytesIO, filename: str = None):
assert isinstance(data, (bytes, BytesIO))
assert isinstance(filename, str)
self._data = data
self.filename = filename

def __repr__(self):
return f"""<{self.__class__.__name__} filename="{self.filename}", size={len(self._data)}>"""

def get_filename(self):
"""
returns the name of the file

:return: name of the file
"""
return self.filename

def get_size(self):
"""
returns the size of the files data in bytes

:return: the size of the files data in bytes
"""
return len(self._data.getvalue() if isinstance(self._data, BytesIO) else self._data)

def as_bytes(self):
"""
returns the content of the file as ``bytes``

:return: file-data
"""
return self._data.getvalue() if isinstance(self._data, BytesIO) else self._data

def as_text(self, encoding: str = None):
"""
decodes the whole content of the file as a string and returns it

:param encoding: (optional) if set, the content of the file is decoded using this encoding, otherwise the
encoding is automatically guessed
:return: content of the file as a ``string``
"""
data = self._data.getvalue() if isinstance(self._data, BytesIO) else self._data
if encoding is None:
encoding = self.guess_text_encoding()['encoding']
return data.decode(encoding)

async def save_dialog(self, prompt: str = "Please select a file to save to:"):
"""
asks the user where to save the file and saves it

:param prompt: (optional) the prompt the user will read
"""
data = self._data.getvalue() if isinstance(self._data, BytesIO) else self._data
await Dialog._save_file_dialog(prompt=prompt, data=data)

@classmethod
async def open_dialog(cls, prompt: str = "Please select a file to open:"):
"""
asks the user for a file to open

:param prompt: (optional) the prompt the user will read
:return: ``File``-object
"""
return await Dialog._open_file_dialog(prompt=prompt)

def download(self):
data = self._data.getvalue() if isinstance(self._data, BytesIO) else self._data
save_file(data = data, filename = self.filename)


class File(FileBase):
"""
Represents an opened file or data. Used to open files from the user or build files for the user to download.

Expand All @@ -23,12 +96,7 @@ class File:
}

def __init__(self, data: bytes, filename: str = None):
assert isinstance(data, bytes)
self._data = data
self.filename = filename

def __repr__(self):
return f"""<{self.__class__.__name__} filename="{self.filename}", size={len(self._data)}>"""
super().__init__(data,filename)

@classmethod
def from_string(cls, text: str, filename: str = 'text.txt'):
Expand Down Expand Up @@ -76,41 +144,6 @@ def from_table(cls, table: list[list[str, ...]] | list[dict[str, str]], header:
raise ValueError("Only .csv and .xlsx are supported file extensions.")
return File(data=data, filename=filename)

def get_filename(self):
"""
returns the name of the file

:return: name of the file
"""
return self.filename

def get_size(self):
"""
returns the size of the files data in bytes

:return: the size of the files data in bytes
"""
return len(self._data)

def as_bytes(self):
"""
returns the content of the file as ``bytes``

:return: file-data
"""
return self._data

def as_text(self, encoding: str = None):
"""
decodes the whole content of the file as a string and returns it

:param encoding: (optional) if set, the content of the file is decoded using this encoding, otherwise the encoding is automatically guessed
:return: content of the file as a ``string``
"""
if encoding is None:
encoding = self.guess_text_encoding()['encoding']
return self._data.decode(encoding)

def as_object_from_json(self):
"""
parses JSON-data to a python-object representing the decoded data of the json-file, throws an exception if the file is not a valid JSON-file
Expand Down Expand Up @@ -194,26 +227,41 @@ def get_all_text_encoding_guesses(self):
"""
return chardet.detect_all(self._data)

async def save_dialog(self, prompt: str = "Please select a file to save to:"):

class ZipFile(FileBase):
def __init__(self, filename: str, mode: t.Literal['r', 'w', 'x', 'a'] = 'a'):
super().__init__(BytesIO(), filename)
self.mode = mode

def add(self, file: File):
"""
asks the user where to save the file and saves it
Adds a file to the zip archive
"""
with self.zip_file as zip_file:
zip_file.writestr(file.get_filename(), file.as_text())

:param prompt: (optional) the prompt the user will read
@property
def zip_file(self):
"""
Create an instance form a zipfile.ZipFile with the contents of self._data and return it
:return: zipfile.ZipFile instance
"""
await Dialog._save_file_dialog(prompt=prompt, data=self._data)
return zipfile.ZipFile(self._data, self.mode, zipfile.ZIP_DEFLATED)

@classmethod
async def open_dialog(cls, prompt: str = "Please select a file to open:"):
def infolist(self):
"""
asks the user for a file to open
Proxy function for ``zipfile.ZipFile.infolist``
"""
return self.zip_file.infolist()

:param prompt: (optional) the prompt the user will read
:return: ``File``-object
def namelist(self):
"""
return await Dialog._open_file_dialog(prompt=prompt)
Proxy function for ``zipfile.ZipFile.namelist``
"""
return self.zip_file.namelist()

def download(self):
def printdir(self):
"""
downloads the file to the users download-directory
Proxy function for ``zipfile.ZipFile.namelist``
"""
save_file(data=self._data, filename=self.filename)
return self.zip_file.printdir()