mirror of
https://github.com/rio-labs/rio.git
synced 2026-02-09 07:09:00 -06:00
random small fixes all over the place
This commit is contained in:
@@ -459,7 +459,7 @@ class App:
|
||||
fastapi_app = app.as_fastapi()
|
||||
```
|
||||
|
||||
You can then run this app with uvicorn:
|
||||
You can then run this app via `uvicorn`:
|
||||
|
||||
```sh
|
||||
uvicorn my_app:fastapi_app
|
||||
|
||||
@@ -94,7 +94,7 @@ def range_requests_response(
|
||||
}
|
||||
|
||||
if media_type is None:
|
||||
# There have been issues with javascript files because browsers insist
|
||||
# There have been issues with JavaScript files because browsers insist
|
||||
# on the mime type "text/javascript", but some PCs aren't configured
|
||||
# correctly and return "text/plain". So we purposely avoid using
|
||||
# `mimetypes.guess_type` for javascript files.
|
||||
|
||||
@@ -655,7 +655,7 @@ class Component(abc.ABC, metaclass=ComponentMeta):
|
||||
the values of `margin`, `margin_x` and `margin_left`.
|
||||
"""
|
||||
|
||||
return utils.first_non_null(
|
||||
return utils.first_non_none(
|
||||
self.margin_left,
|
||||
self.margin_x,
|
||||
self.margin,
|
||||
@@ -669,7 +669,7 @@ class Component(abc.ABC, metaclass=ComponentMeta):
|
||||
the values of `margin`, `margin_y` and `margin_top`.
|
||||
"""
|
||||
|
||||
return utils.first_non_null(
|
||||
return utils.first_non_none(
|
||||
self.margin_top,
|
||||
self.margin_y,
|
||||
self.margin,
|
||||
@@ -683,7 +683,7 @@ class Component(abc.ABC, metaclass=ComponentMeta):
|
||||
the values of `margin`, `margin_right` and `margin_x`.
|
||||
"""
|
||||
|
||||
return utils.first_non_null(
|
||||
return utils.first_non_none(
|
||||
self.margin_right,
|
||||
self.margin_x,
|
||||
self.margin,
|
||||
@@ -697,7 +697,7 @@ class Component(abc.ABC, metaclass=ComponentMeta):
|
||||
the values of `margin`, `margin_y` and `margin_bottom`.
|
||||
"""
|
||||
|
||||
return utils.first_non_null(
|
||||
return utils.first_non_none(
|
||||
self.margin_bottom,
|
||||
self.margin_y,
|
||||
self.margin,
|
||||
|
||||
@@ -134,12 +134,12 @@ class FlowContainer(FundamentalComponent):
|
||||
|
||||
def _custom_serialize_(self) -> JsonDoc:
|
||||
result: JsonDoc = {
|
||||
"row_spacing": utils.first_non_null(
|
||||
"row_spacing": utils.first_non_none(
|
||||
self.row_spacing,
|
||||
self.spacing,
|
||||
0,
|
||||
),
|
||||
"column_spacing": utils.first_non_null(
|
||||
"column_spacing": utils.first_non_none(
|
||||
self.column_spacing,
|
||||
self.spacing,
|
||||
0,
|
||||
|
||||
@@ -69,7 +69,7 @@ document.head.appendChild(style);
|
||||
|
||||
class FundamentalComponent(Component):
|
||||
# Unique id for identifying this class in the frontend. This is initialized
|
||||
# in `Component.__init_subclass__`.
|
||||
# in `__init_subclass__`.
|
||||
_unique_id_: t.ClassVar[str]
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
|
||||
@@ -217,6 +217,7 @@ class Plot(FundamentalComponent):
|
||||
|
||||
# Matplotlib (+ Seaborn)
|
||||
elif isinstance(figure, maybes.MATPLOTLIB_GRAPH_TYPES):
|
||||
# Seaborn "figures" are actually matplotlib `Axes` objects
|
||||
if isinstance(figure, maybes.MATPLOTLIB_AXES_TYPES):
|
||||
figure = figure.figure
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class Dialog(t.Generic[T]):
|
||||
|
||||
def __init__(self) -> None:
|
||||
raise RuntimeError(
|
||||
"Dialogs cannot be instantiated directly. To create a dialog, call `self.session.show_custom_dialog` inside of a component's event handler."
|
||||
"Dialogs cannot be instantiated directly. To create a dialog, call `await self.session.show_custom_dialog(...)` inside of a component's event handler."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# and Sunday being 6.
|
||||
#
|
||||
# The most common first day of the week is Monday, and has been omitted from
|
||||
# the list - use it as default values for missing languages.
|
||||
# the list - use it as default value for missing languages.
|
||||
#
|
||||
# These were exported from Babel on 2024-06-01:
|
||||
#
|
||||
@@ -291,9 +291,9 @@ def get_week_start_day(language_tag: str) -> int:
|
||||
|
||||
The result is a zero-based index, with Monday being 0 and Sunday being 6.
|
||||
"""
|
||||
# According to stackoverflow some browsers (you know it's safari) send
|
||||
# According to stackoverflow some browsers (you just know it's safari) send
|
||||
# invalid, lowercase tags. Normalize the tag to avoid issues.
|
||||
language_tag = language_tag.lower()
|
||||
|
||||
# Then look up, defaulting to Monday
|
||||
# Look up the value, defaulting to Monday
|
||||
return _RFC_TO_WEEK_START_DAY.get(language_tag, 0)
|
||||
|
||||
@@ -41,7 +41,8 @@ PLOTLY_GRAPH_TYPES: tuple[type[plotly.graph_objects.Figure], ...] = ()
|
||||
MATPLOTLIB_GRAPH_TYPES: tuple[type, ...] = ()
|
||||
MATPLOTLIB_AXES_TYPES: tuple[type[matplotlib.axes.Axes], ...] = ()
|
||||
|
||||
# This is a mapping of "weird" types to the "canonical" type, like `{np.int8: int}`
|
||||
# This is a mapping of "weird" types to the "canonical" type, like `{np.int8:
|
||||
# int}`
|
||||
TYPE_NORMALIZERS: dict[type[T], t.Callable[[T], T]] = {} # type: ignore
|
||||
|
||||
|
||||
@@ -51,12 +52,15 @@ def initialize(force: bool = False) -> None:
|
||||
is not automatically done on module load, to make sure any needed modules
|
||||
have already been imported - some functionality is not initialized if those
|
||||
other modules aren't used.
|
||||
|
||||
If `force` is `True` everything is initialized even if it was already
|
||||
initialized previously.
|
||||
"""
|
||||
global _IS_INITIALIZED
|
||||
global FLOAT_TYPES, INT_TYPES, BOOL_TYPES, STR_TYPES
|
||||
global NUMPY_ARRAY_TYPES
|
||||
global PANDAS_DATAFRAME_TYPES, POLARS_DATAFRAME_TYPES
|
||||
global PLOTLY_GRAPH_TYPES, MATPLOTLIB_GRAPH_TYPES
|
||||
global PLOTLY_GRAPH_TYPES, MATPLOTLIB_GRAPH_TYPES, MATPLOTLIB_AXES_TYPES
|
||||
|
||||
# Already initialized?
|
||||
if _IS_INITIALIZED and not force:
|
||||
|
||||
94
rio/utils.py
94
rio/utils.py
@@ -28,8 +28,26 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
# Sentinel type/value for when no value is provided and `None` would be
|
||||
# unacceptable
|
||||
# Sentinel type & value for when no value is provided and `None` would be
|
||||
# unacceptable.
|
||||
#
|
||||
# To use this, use `NotGiven` as the type hint and `NOT_GIVEN` as the default
|
||||
# value. Then, for checking the value, use `isinstance(value, NotGiven)`. While
|
||||
# `value is NOT_GIVEN` would work, it is not recommended because it makes the
|
||||
# type checker believe that the value could be another instance of `NotGiven`,
|
||||
# even though there are no other instances.
|
||||
#
|
||||
# ## Example
|
||||
#
|
||||
# ```python
|
||||
# def my_function(value: NotGiven = NOT_GIVEN) -> None:
|
||||
# if isinstance(value, NotGiven):
|
||||
# raise ValueError("You fool!")
|
||||
#
|
||||
# # Do something with the value
|
||||
# ```
|
||||
|
||||
|
||||
class NotGiven:
|
||||
pass
|
||||
|
||||
@@ -76,7 +94,7 @@ ASSET_MANAGER: imy.assets.AssetManager = imy.assets.AssetManager(
|
||||
)
|
||||
|
||||
|
||||
# Precompiled regexes
|
||||
# Precompiled regex patterns
|
||||
MARKDOWN_ESCAPE = re.compile(r"([\\`\*_\{\}\[\]\(\)#\+\-.!])")
|
||||
MARKDOWN_CODE_ESCAPE = re.compile(r"([\\`])")
|
||||
|
||||
@@ -114,11 +132,11 @@ class FileInfo:
|
||||
"""
|
||||
Contains information about a file.
|
||||
|
||||
When asking the user to select a file, this class is used to represent the
|
||||
file. It contains metadata about the file, and can also be used to access
|
||||
the file's contents.
|
||||
When asking the user to pick a file, this class is used to represent the
|
||||
chosen file. It contains metadata about the file, and can also be used to
|
||||
access the file's contents.
|
||||
|
||||
Be careful when running your app as a webserver, since files will need to be
|
||||
Be careful when running your app as a website, since files will need to be
|
||||
uploaded by the user, which is a potentially very slow operation.
|
||||
|
||||
|
||||
@@ -144,6 +162,8 @@ class FileInfo:
|
||||
Reads and returns the entire file as a `bytes` object. If you know that
|
||||
the file is text, consider using `read_text` instead.
|
||||
"""
|
||||
# TODO: Files are currently read in their entirety, immediately. Change
|
||||
# that so that they are only fetched once this function is called.
|
||||
return self._contents
|
||||
|
||||
async def read_text(self, *, encoding: str = "utf-8") -> str:
|
||||
@@ -154,13 +174,15 @@ class FileInfo:
|
||||
using the given `encoding`. If you don't know that the file is valid
|
||||
text, use `read_bytes` instead.
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
encoding: The encoding to use when decoding the file.
|
||||
|
||||
|
||||
## Raises
|
||||
|
||||
UnicodeDecodeError: The file could not be decoded using the given
|
||||
`UnicodeDecodeError`: If the file could not be decoded using the given
|
||||
`encoding`.
|
||||
"""
|
||||
return self._contents.decode(encoding)
|
||||
@@ -212,7 +234,7 @@ def make_url_relative(base: URL, other: URL) -> URL:
|
||||
`other` is not a child of `base`.
|
||||
|
||||
This will never generate URLs containing `..`. If those would be needed a
|
||||
`ValueError` is raised.
|
||||
`ValueError` is raised instead.
|
||||
"""
|
||||
# Verify the URLs have the same scheme and host
|
||||
if base.scheme != other.scheme:
|
||||
@@ -294,7 +316,7 @@ def port_is_free(host: str, port: int) -> bool:
|
||||
try:
|
||||
sock.bind((host, port))
|
||||
return True
|
||||
except OSError as err:
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
@@ -305,7 +327,7 @@ def ensure_valid_port(host: str, port: int | None) -> int:
|
||||
return port
|
||||
|
||||
|
||||
def first_non_null(*values: T | None) -> T:
|
||||
def first_non_none(*values: T | None) -> T:
|
||||
"""
|
||||
Returns the first non-`None` value, or raises a `ValueError` if all values
|
||||
are `None`.
|
||||
@@ -410,6 +432,10 @@ def normalize_url(url: rio.URL) -> rio.URL:
|
||||
|
||||
|
||||
def is_python_script(path: Path) -> bool:
|
||||
"""
|
||||
Guesses whether the path points to a Python script, based on the path's file
|
||||
extension.
|
||||
"""
|
||||
return path.suffix in (".py", ".pyc", ".pyd", ".pyo", ".pyw")
|
||||
|
||||
|
||||
@@ -425,27 +451,30 @@ def normalize_file_type(file_type: str) -> str:
|
||||
This is best-effort. If the input type is invalid or unknown, the cleaned
|
||||
input may not be accurate.
|
||||
|
||||
Examples:
|
||||
>>> standardize_file_type("pdf")
|
||||
'pdf'
|
||||
>>> standardize_file_type(".PDF")
|
||||
'pdf'
|
||||
>>> standardize_file_type("*.pdf")
|
||||
'pdf'
|
||||
>>> standardize_file_type("application/pdf")
|
||||
'pdf'
|
||||
## Examples
|
||||
|
||||
```py
|
||||
>>> standardize_file_type("pdf")
|
||||
'pdf'
|
||||
>>> standardize_file_type(".PDF")
|
||||
'pdf'
|
||||
>>> standardize_file_type("*.pdf")
|
||||
'pdf'
|
||||
>>> standardize_file_type("application/pdf")
|
||||
'pdf'
|
||||
```
|
||||
"""
|
||||
# Normalize the input string
|
||||
file_type = file_type.lower().strip()
|
||||
|
||||
# If this is a MIME type, guess the extension
|
||||
if "/" in file_type:
|
||||
guessed_type = mimetypes.guess_extension(file_type, strict=False)
|
||||
guessed_suffix = mimetypes.guess_extension(file_type, strict=False)
|
||||
|
||||
if guessed_type is None:
|
||||
if guessed_suffix is None:
|
||||
file_type = file_type.rsplit("/", 1)[-1]
|
||||
else:
|
||||
file_type = guessed_type.lstrip(".")
|
||||
file_type = guessed_suffix.lstrip(".")
|
||||
|
||||
# If it isn't a MIME type, convert it to one anyway. Some file types have
|
||||
# multiple commonly used extensions. This will always map them to the same
|
||||
@@ -455,22 +484,27 @@ def normalize_file_type(file_type: str) -> str:
|
||||
f"file.{file_type}", strict=False
|
||||
)
|
||||
|
||||
if guessed_type is None:
|
||||
file_type = file_type.lstrip(".*")
|
||||
else:
|
||||
file_type = file_type.lstrip(".*")
|
||||
|
||||
if guessed_type is not None:
|
||||
guessed_type = mimetypes.guess_extension(
|
||||
guessed_type,
|
||||
strict=False,
|
||||
)
|
||||
|
||||
assert guessed_type is not None
|
||||
file_type = guessed_type.lstrip(".")
|
||||
# Yes, this really happens on some systems. For some reason, we can
|
||||
# get the type for a suffix, but not the suffix for the same type.
|
||||
if guessed_type is not None:
|
||||
file_type = guessed_type.lstrip(".")
|
||||
|
||||
# Done
|
||||
return file_type
|
||||
|
||||
|
||||
def soft_sort(elements: list[T], key: t.Callable[[T], int | None]) -> None:
|
||||
def soft_sort(
|
||||
elements: list[T],
|
||||
key: t.Callable[[T], int | None],
|
||||
) -> None:
|
||||
"""
|
||||
Sorts the given list in-place, allowing for `None` values in the key.
|
||||
|
||||
@@ -486,7 +520,7 @@ def soft_sort(elements: list[T], key: t.Callable[[T], int | None]) -> None:
|
||||
Note that there is one special case where this may not work as intended: If
|
||||
two items have the same key assigned they obviously cannot be placed at the
|
||||
same position. In that case the original order between them is preserved,
|
||||
and followup items shifted as needed.
|
||||
and subsequent items shifted as needed.
|
||||
"""
|
||||
|
||||
# To ensure we can keep the original order, add the current index to all
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class RioDeprecationWarning(Warning):
|
||||
"""
|
||||
The user used/did something that's deprecated.
|
||||
The user used functionality that has been deprecated.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user