docs docs docs

This commit is contained in:
Jesse Bannon 2023-12-14 22:51:31 -08:00
parent ea701efd91
commit 720d4e97e4
11 changed files with 176 additions and 24 deletions

View file

@ -1,3 +1,4 @@
# pylint: disable=protected-access
from abc import ABC
from pathlib import Path
from typing import Any
@ -35,7 +36,7 @@ class BaseEntry(ABC):
self._kwargs = entry_dict
@property
def uid(self: "BaseEntry") -> str:
def uid(self) -> str:
"""
Returns
-------
@ -45,7 +46,7 @@ class BaseEntry(ABC):
return str(self._kwargs[v.uid.metadata_key])
@property
def download_archive_extractor(self: "BaseEntry") -> str:
def download_archive_extractor(self) -> str:
"""
The extractor name used in yt-dlp download archives
"""
@ -59,14 +60,14 @@ class BaseEntry(ABC):
).lower()
@property
def title(self: "BaseEntry") -> str:
def title(self) -> str:
"""
The title of the entry. If a title does not exist, returns its unique ID.
"""
return self._kwargs_get(v.title.metadata_key, self.uid)
@property
def webpage_url(self: "BaseEntry") -> str:
def webpage_url(self) -> str:
"""
The url to the webpage.
"""
@ -78,7 +79,7 @@ class BaseEntry(ABC):
return "info.json"
@property
def uploader_id(self: "BaseEntry") -> str:
def uploader_id(self) -> str:
"""
The uploader id if it exists, otherwise return the unique ID.
"""

View file

@ -1,3 +1,4 @@
# pylint: disable=protected-access
import copy
import json
import os
@ -75,6 +76,10 @@ class Entry(BaseEntry, Scriptable):
def add_injected_variables(
self, download_entry: "Entry", download_idx: int, upload_date_idx: int
) -> "Entry":
"""
Adds variables that get injected into the Entry script that aren't available at
metadata scrape time (only after the actual download).
"""
self.add(
{
# Tracks number of entries downloaded
@ -211,7 +216,7 @@ class Entry(BaseEntry, Scriptable):
return maybe_prior_variables
@final
def to_dict(self) -> Dict[str, str]:
def to_dict(self) -> Dict[str, Any]:
"""
Returns
-------

View file

@ -31,10 +31,25 @@ class Functions(
@classmethod
def is_built_in(cls, name: str) -> bool:
"""
Returns
-------
True if the name exists as a built-in function or custom function. False otherwise.
"""
return hasattr(cls, name) or hasattr(cls, f"{name}_") or name in cls._custom_functions
@classmethod
def get(cls, name: str) -> Callable[..., Resolvable]:
"""
Returns
-------
The actual Python callable for the function of the given name.
Raises
------
FunctionDoesNotExistRuntimeException
If the function does not exist.
"""
if hasattr(cls, name):
return getattr(cls, name)
if hasattr(cls, f"{name}_"):
@ -46,6 +61,14 @@ class Functions(
@classmethod
def register_function(cls, function: Callable[..., Resolvable]) -> None:
"""
Adds a function to the suite of offered functions.
Parameters
----------
function
A static function whose name will be used as the offered function name.
"""
if cls.is_built_in(function.__name__):
raise ValueError(
f"Cannot register a function with name {function.__name__} "

View file

@ -18,6 +18,14 @@ from ytdl_sub.script.utils.exceptions import FunctionRuntimeException
class ArrayFunctions:
@staticmethod
def array(maybe_array: AnyArgument) -> Array:
"""
Tries to cast an unknown variable type to an Array.
Raises
------
FunctionRuntimeException
If the input type is not actually an Array.
"""
if not isinstance(maybe_array, Array):
raise FunctionRuntimeException(
f"Tried and failed to cast {maybe_array.type_name()} as an Array"
@ -26,6 +34,9 @@ class ArrayFunctions:
@staticmethod
def array_size(array: Array) -> Integer:
"""
Returns the size of an Array.
"""
return Integer(len(array.value))
@staticmethod

View file

@ -33,5 +33,8 @@ def _from_json(out: Any) -> Resolvable:
class JsonFunctions:
@staticmethod
def from_json(argument: AnyArgument) -> AnyArgument:
def from_json(argument: String) -> AnyArgument:
"""
Converts a JSON string into an actual type.
"""
return _from_json(json.loads(argument.value))

View file

@ -17,6 +17,14 @@ from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException
class MapFunctions:
@staticmethod
def map(maybe_mapping: AnyArgument) -> Map:
"""
Tries to cast an unknown variable type to a Map.
Raises
------
FunctionRuntimeException
If the input type is not actually a Map.
"""
if not isinstance(maybe_mapping, Map):
raise FunctionRuntimeException(
f"Tried and failed to cast {maybe_mapping.type_name()} as a Map"
@ -25,6 +33,9 @@ class MapFunctions:
@staticmethod
def map_size(mapping: Map) -> Integer:
"""
Returns the size of a Map.
"""
return Integer(len(mapping.value))
@staticmethod

View file

@ -144,11 +144,9 @@ class Script:
if lambda_type := spec.is_lambda_like:
lambda_function_names = set(
[
lamb.value
for lamb in SyntaxTree(function.args).lambdas
if isinstance(lamb, Lambda)
]
lamb.value
for lamb in SyntaxTree(function.args).lambdas
if isinstance(lamb, Lambda)
)
# Only case len(lambda_function_names) > 1 is when used in if-statements
@ -342,11 +340,46 @@ class Script:
unresolvable: Optional[Set[str]] = None,
update: bool = False,
) -> ScriptOutput:
"""
Resolves the script
Parameters
----------
resolved
Optional. Pre-resolved variables that should be used instead of what is in the script.
unresolvable
Optional. Unresolvable variables that will be ignored in resolution, including all
variables with a dependency to them.
update
Whether to update the script's internal values with the resolved variables instead of
their original definition. This helps avoid re-evaluated the same variables repeatedly.
Returns
-------
ScriptOutput
Containing all resolved variables.
"""
return self._resolve(
pre_resolved=resolved, unresolvable=unresolvable, update=update, output_filter=None
)
def add(self, variables: Dict[str, str], unresolvable: Optional[Set[str]] = None) -> "Script":
"""
Adds parses and adds new variables to the script.
Parameters
----------
variables
Mapping containing variable name to definition.
unresolvable
Optional. Set of unresolved variables that the new variables may contain, but the
script does not (yet).
Returns
-------
Script
self
"""
added_variables_to_validate: Set[str] = set()
for variable_name, variable_definition in variables.items():
self._variables[variable_name] = parse(

View file

@ -10,13 +10,25 @@ from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
class ScriptOutput:
output: Dict[str, Resolvable]
def as_resolvable(self) -> Dict[str, Resolvable]:
return self.output
def as_native(self) -> Dict[str, Any]:
"""
Returns
-------
The script output as native python types
"""
return {name: out.native for name, out in self.output.items()}
def get(self, name: str) -> Resolvable:
"""
Returns
-------
The script output's variable as a resolvable type
Raises
------
ScriptVariableNotResolved
The variable name requested did not resolve
"""
if name not in self.output:
raise ScriptVariableNotResolved(
f"Tried to access resolved variable {name}, but it has not resolved"
@ -24,12 +36,17 @@ class ScriptOutput:
return self.output[name]
def get_native(self, name: str) -> Any:
"""
Returns
-------
The script output's variable as native python type
"""
return self.get(name).native
def get_str(self, name: str) -> str:
"""
Returns
-------
The script output's variable as a string
"""
return str(self.get(name))
def get_int(self, name: str) -> int:
out = self.get_native(name)
assert isinstance(out, int)
return out

View file

@ -73,22 +73,36 @@ class ReturnableArgumentB(ValueArgument, NamedType, ABC):
@dataclass(frozen=True)
class AnyArgument(ReturnableArgument, ReturnableArgumentA, ReturnableArgumentB, ABC):
"""
Human-readable name for FutureResolvable
Human-readable name for Resolvable
"""
@dataclass(frozen=True)
class Resolvable(AnyArgument, ABC):
"""
A type that is resolved into a native Python type (and have no dependencies to other types).
"""
def __str__(self) -> str:
return str(self.value)
@property
def native(self) -> Any:
"""
Returns
-------
The resolvable in its native form
"""
return self.value
@dataclass(frozen=True)
class FutureResolvable(AnyArgument, ABC):
"""
Used when parsing, it is an unresolved type that will eventually resolve to a known type
(i.e. Maps, Arrays)
"""
@abstractmethod
def future_resolvable_type(self) -> Type[Resolvable]:
pass
@ -96,27 +110,47 @@ class FutureResolvable(AnyArgument, ABC):
@dataclass(frozen=True)
class Hashable(Resolvable, ABC):
"""
Resolvable type that can be used as hashes (i.e. in Maps)
"""
pass
@dataclass(frozen=True)
class NonHashable(NamedType, ABC):
"""
Type that is known to never be hashable.
"""
pass
@dataclass(frozen=True)
class ResolvableToJson(Resolvable, ABC):
"""
Types whose string values should be resolved to JSON (i.e. Maps, Arrays)
"""
def __str__(self):
return json.dumps(self.native)
@dataclass(frozen=True)
class ResolvableT(Hashable, ABC, Generic[T]):
"""
Resolvable types that resolve to the generic T
"""
value: T
@dataclass(frozen=True)
class Numeric(ResolvableT[NumericT], Hashable, ABC, Generic[NumericT]):
class Numeric(ResolvableT[NumericT], ABC, Generic[NumericT]):
"""
Resolvable numeric types (int/float)
"""
pass
@ -131,12 +165,12 @@ class Float(Numeric[float], Argument):
@dataclass(frozen=True)
class Boolean(ResolvableT[bool], Hashable, Argument):
class Boolean(ResolvableT[bool], Argument):
pass
@dataclass(frozen=True)
class String(ResolvableT[str], Hashable, Argument):
class String(ResolvableT[str], Argument):
pass
@ -199,4 +233,8 @@ class LambdaThree(Lambda):
@dataclass(frozen=True)
class LambdaReduce(LambdaTwo):
"""
Type-hinting for functions that apply a reduce-operation using a lambda (two arguments)
"""
pass

View file

@ -42,6 +42,11 @@ class SyntaxTree(VariableDependency):
@property
def maybe_resolvable(self) -> Optional[Resolvable]:
"""
Returns
-------
A resolvable if the AST contains a single type that is resolvable. None otherwise.
"""
if len(self.ast) == 1 and isinstance(self.ast[0], Resolvable):
return self.ast[0]
return None

View file

@ -106,6 +106,11 @@ def is_type_compatible(
arg: NamedType,
expected_arg_type: Type[Resolvable | Optional[Resolvable]],
) -> bool:
"""
Returns
-------
True if arg is compatible with expected_arg_type. False otherwise.
"""
arg_type: Type[NamedType] = arg.__class__
if isinstance(arg, BuiltInFunctionType):
arg_type = arg.output_type() # built-in function