From e6c32195860d569c8ca67f147afe3eb471567e45 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Wed, 22 Nov 2023 00:13:45 +0100 Subject: [PATCH] Add stubs for fanstatic (#9931) --- stubs/fanstatic/@tests/stubtest_allowlist.txt | 77 ++++++ stubs/fanstatic/METADATA.toml | 3 + stubs/fanstatic/fanstatic/__init__.pyi | 43 ++++ stubs/fanstatic/fanstatic/checksum.pyi | 10 + stubs/fanstatic/fanstatic/compiler.pyi | 125 +++++++++ stubs/fanstatic/fanstatic/config.pyi | 10 + stubs/fanstatic/fanstatic/core.pyi | 237 ++++++++++++++++++ stubs/fanstatic/fanstatic/inclusion.pyi | 22 ++ stubs/fanstatic/fanstatic/injector.pyi | 57 +++++ stubs/fanstatic/fanstatic/publisher.pyi | 49 ++++ stubs/fanstatic/fanstatic/registry.pyi | 45 ++++ stubs/fanstatic/fanstatic/wsgi.pyi | 22 ++ 12 files changed, 700 insertions(+) create mode 100644 stubs/fanstatic/@tests/stubtest_allowlist.txt create mode 100644 stubs/fanstatic/METADATA.toml create mode 100644 stubs/fanstatic/fanstatic/__init__.pyi create mode 100644 stubs/fanstatic/fanstatic/checksum.pyi create mode 100644 stubs/fanstatic/fanstatic/compiler.pyi create mode 100644 stubs/fanstatic/fanstatic/config.pyi create mode 100644 stubs/fanstatic/fanstatic/core.pyi create mode 100644 stubs/fanstatic/fanstatic/inclusion.pyi create mode 100644 stubs/fanstatic/fanstatic/injector.pyi create mode 100644 stubs/fanstatic/fanstatic/publisher.pyi create mode 100644 stubs/fanstatic/fanstatic/registry.pyi create mode 100644 stubs/fanstatic/fanstatic/wsgi.pyi diff --git a/stubs/fanstatic/@tests/stubtest_allowlist.txt b/stubs/fanstatic/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000000..489db9ece9d6 --- /dev/null +++ b/stubs/fanstatic/@tests/stubtest_allowlist.txt @@ -0,0 +1,77 @@ +# Error: is not present in stub +# ============================= +# These are methods and attributes that really should have been +# prefixed with a `_`, since they should only really be used +# internally +fanstatic.Library.init_library_nr +fanstatic.core.Asset.init_dependency_nr +fanstatic.core.Library.init_library_nr + +# In order to catch errors where DummyNeededResources would be called +# with clear/library_url/resources these methods were dropped from the +# stub. We would prefer to use @type_error() once that is an option +fanstatic.core.DummyNeededResources.clear +fanstatic.core.DummyNeededResources.library_url +fanstatic.core.DummyNeededResources.resources + +# Error: is inconsistent +# ====================== +# The core API for Dependable is a bit annoying, since the base class +# should really be abstract and instead defines some attributes as +# None, even though all subclasses populate them, so these have been +# made abstract to make defining correct subclasses more easy +fanstatic.Group.depends +fanstatic.Group.resources +fanstatic.Group.supports +fanstatic.core.Asset.depends +fanstatic.core.Asset.resources +fanstatic.core.Asset.supports +fanstatic.core.Dependable.depends +fanstatic.core.Dependable.resources +fanstatic.core.Dependable.supports +fanstatic.core.Group.depends +fanstatic.core.Group.resources +fanstatic.core.Group.supports + +# The API for Compiler has very much the same problem, so these are +# some more attributes/methods that have been made abstract for the +# purposes of type checking +fanstatic.Compiler.name +fanstatic.Compiler.source_extension +fanstatic.Minifier.name +fanstatic.Minifier.source_extension +fanstatic.Minifier.target_extension +fanstatic.compiler.CommandlineBase.command +fanstatic.compiler.Compiler.name +fanstatic.compiler.Compiler.source_extension +fanstatic.compiler.Minifier.name +fanstatic.compiler.Minifier.source_extension +fanstatic.compiler.Minifier.target_extension +fanstatic.compiler.NullCompiler.name +fanstatic.registry.Registry.ENTRY_POINT + +# This is only inconsistent because the library authors set this +# attribute to `None` on the class, so they could assign a docstring +# to it. `__init__` will always populate this attribute with a `str` +fanstatic.Library.path +fanstatic.core.Library.path + +# Error: variable differs from runtime type +# ====================== +# These are some sentinel objects which use the NewType pattern to create a +# distinct type +fanstatic.compiler.SOURCE +fanstatic.compiler.TARGET +fanstatic.core.NOTHING +fanstatic.core.REQUIRED_DEFAULT_MARKER + +# Error: is not present at runtime +# ================================ +# See above, defining correct subclasses is more easy with abstract +# properties on the superclass +fanstatic.injector.InjectorPlugin.name + +# Error: failed to find stubs +# =========================== +# Tests should not be part of the stubs +fanstatic.tests.* diff --git a/stubs/fanstatic/METADATA.toml b/stubs/fanstatic/METADATA.toml new file mode 100644 index 000000000000..f0e10ed21df4 --- /dev/null +++ b/stubs/fanstatic/METADATA.toml @@ -0,0 +1,3 @@ +version = "1.4.*" +upstream_repository = "https://github.com/zopefoundation/fanstatic" +requires = ["types-setuptools", "types-WebOb"] diff --git a/stubs/fanstatic/fanstatic/__init__.pyi b/stubs/fanstatic/fanstatic/__init__.pyi new file mode 100644 index 000000000000..9527cb5fb528 --- /dev/null +++ b/stubs/fanstatic/fanstatic/__init__.pyi @@ -0,0 +1,43 @@ +from fanstatic.compiler import Compiler as Compiler, Minifier as Minifier, sdist_compile as sdist_compile +from fanstatic.core import ( + BUNDLE_PREFIX as BUNDLE_PREFIX, + DEBUG as DEBUG, + DEFAULT_SIGNATURE as DEFAULT_SIGNATURE, + MINIFIED as MINIFIED, + NEEDED as NEEDED, + VERSION_PREFIX as VERSION_PREFIX, + ConfigurationError as ConfigurationError, + Group as Group, + GroupResource as GroupResource, + Library as Library, + LibraryDependencyCycleError as LibraryDependencyCycleError, + NeededResources as NeededResources, + Resource as Resource, + Slot as Slot, + SlotError as SlotError, + UnknownResourceError as UnknownResourceError, + UnknownResourceExtension as UnknownResourceExtension, + UnknownResourceExtensionError as UnknownResourceExtensionError, + clear_needed as clear_needed, + del_needed as del_needed, + get_needed as get_needed, + init_needed as init_needed, + register_inclusion_renderer as register_inclusion_renderer, + set_auto_register_library as set_auto_register_library, + set_resource_file_existence_checking as set_resource_file_existence_checking, +) +from fanstatic.inclusion import Inclusion as Inclusion, bundle_resources as bundle_resources, sort_resources as sort_resources +from fanstatic.injector import Injector as Injector, make_injector as make_injector +from fanstatic.publisher import ( + Delegator as Delegator, + LibraryPublisher as LibraryPublisher, + Publisher as Publisher, + make_publisher as make_publisher, +) +from fanstatic.registry import ( + CompilerRegistry as CompilerRegistry, + LibraryRegistry as LibraryRegistry, + MinifierRegistry as MinifierRegistry, + get_library_registry as get_library_registry, +) +from fanstatic.wsgi import Fanstatic as Fanstatic, Serf as Serf, make_fanstatic as make_fanstatic, make_serf as make_serf diff --git a/stubs/fanstatic/fanstatic/checksum.pyi b/stubs/fanstatic/fanstatic/checksum.pyi new file mode 100644 index 000000000000..316137739b11 --- /dev/null +++ b/stubs/fanstatic/fanstatic/checksum.pyi @@ -0,0 +1,10 @@ +from _typeshed import GenericPath, StrOrBytesPath, StrPath +from collections.abc import Iterator +from typing import AnyStr + +VCS_NAMES: list[str] +IGNORED_EXTENSIONS: list[str] + +def list_directory(path: GenericPath[AnyStr], include_directories: bool = True) -> Iterator[AnyStr]: ... +def mtime(path: StrOrBytesPath) -> str: ... +def md5(path: StrPath) -> str: ... diff --git a/stubs/fanstatic/fanstatic/compiler.pyi b/stubs/fanstatic/fanstatic/compiler.pyi new file mode 100644 index 000000000000..a3f31d8292bc --- /dev/null +++ b/stubs/fanstatic/fanstatic/compiler.pyi @@ -0,0 +1,125 @@ +from _typeshed import StrOrBytesPath +from abc import abstractmethod +from logging import Logger +from subprocess import Popen +from typing import Any, ClassVar, NewType +from typing_extensions import Literal + +import setuptools.command.sdist +from fanstatic.core import Resource + +logger: Logger + +class CompilerError(Exception): ... + +class Compiler: + @property + @abstractmethod + def name(self) -> str: ... + @property + @abstractmethod + def source_extension(self) -> str: ... + def __call__(self, resource: Resource, force: bool = False) -> None: ... + def process(self, source: StrOrBytesPath, target: StrOrBytesPath) -> Any: ... + def should_process(self, source: StrOrBytesPath, target: StrOrBytesPath) -> bool: ... + @property + @abstractmethod + def available(self) -> bool: ... + def source_path(self, resource: Resource) -> str | None: ... + def target_path(self, resource: Resource) -> str | None: ... + +class Minifier(Compiler): + @property + @abstractmethod + def name(self) -> str: ... + @property + @abstractmethod + def source_extension(self) -> str: ... + @property + @abstractmethod + def target_extension(self) -> str: ... + def source_to_target(self, resource: Resource) -> str: ... + +def compile_resources(argv: list[str] = ...) -> None: ... + +class sdist_compile(setuptools.command.sdist.sdist): ... + +class NullCompiler(Compiler): + name: ClassVar[Literal[""]] + source_extension = NotImplemented + def source_path(self, resource: Resource) -> None: ... + def target_path(self, resource: Resource) -> None: ... + def should_process(self, source: StrOrBytesPath, target: StrOrBytesPath) -> Literal[False]: ... + @property + def available(self) -> Literal[False]: ... + +_SourceType = NewType("_SourceType", object) +_TargetType = NewType("_TargetType", object) +SOURCE: _SourceType +TARGET: _TargetType + +class CommandlineBase: + @property + @abstractmethod + def command(self) -> str: ... + arguments: ClassVar[list[str]] + @property + def available(self) -> bool: ... + def process(self, source: StrOrBytesPath | _SourceType, target: StrOrBytesPath | _TargetType) -> Popen[str]: ... + +class CoffeeScript(CommandlineBase, Compiler): + name: ClassVar[Literal["coffee"]] + command: ClassVar[Literal["coffee"]] + source_extension = NotImplemented + def process( # type:ignore[override] + self, source: StrOrBytesPath | _SourceType, target: StrOrBytesPath | _TargetType + ) -> None: ... + +COFFEE_COMPILER: CoffeeScript + +class LESS(CommandlineBase, Compiler): + name: ClassVar[Literal["less"]] + command: ClassVar[Literal["lessc"]] + source_extension = NotImplemented + +LESS_COMPILER: LESS + +class SASS(CommandlineBase, Compiler): + name: ClassVar[Literal["sass"]] + command: ClassVar[Literal["sass"]] + source_extension: ClassVar[Literal[".scss"]] + +SASS_COMPILER: SASS + +class PythonPackageBase: + @property + @abstractmethod + def package(self) -> str: ... + @property + def available(self) -> bool: ... + +class CSSMin(PythonPackageBase, Minifier): + name: ClassVar[Literal["cssmin"]] + package: ClassVar[Literal["cssmin"]] + source_extension = NotImplemented + target_extension: ClassVar[Literal[".min.css"]] + +CSSMIN_MINIFIER: CSSMin + +class JSMin(PythonPackageBase, Minifier): + name: ClassVar[Literal["jsmin"]] + package: ClassVar[Literal["jsmin"]] + source_extension = NotImplemented + target_extension: ClassVar[Literal[".min.js"]] + +JSMIN_MINIFIER: JSMin + +class Closure(PythonPackageBase, Minifier): + name: ClassVar[Literal["closure"]] + package: ClassVar[Literal["closure"]] + source_extension = NotImplemented + target_extension: Literal[".min.js"] + arguments: ClassVar[list[str]] + def process(self, source: StrOrBytesPath, target: StrOrBytesPath) -> Popen[str]: ... + +CLOSURE_MINIFIER: Closure diff --git a/stubs/fanstatic/fanstatic/config.pyi b/stubs/fanstatic/fanstatic/config.pyi new file mode 100644 index 000000000000..551600e14faf --- /dev/null +++ b/stubs/fanstatic/fanstatic/config.pyi @@ -0,0 +1,10 @@ +from _typeshed import SupportsItems +from typing import TypeVar + +_KT = TypeVar("_KT") +_VT = TypeVar("_VT") + +BOOL_CONFIG: set[str] + +def asbool(obj: object) -> bool: ... +def convert_config(config: SupportsItems[_KT, _VT]) -> dict[_KT, _VT | bool]: ... diff --git a/stubs/fanstatic/fanstatic/core.pyi b/stubs/fanstatic/fanstatic/core.pyi new file mode 100644 index 000000000000..304c74328dad --- /dev/null +++ b/stubs/fanstatic/fanstatic/core.pyi @@ -0,0 +1,237 @@ +from abc import abstractmethod +from collections.abc import Callable, Iterable +from threading import local +from types import ModuleType +from typing import NewType +from typing_extensions import Literal, TypeAlias + +from fanstatic.compiler import Compiler, Minifier + +_Renderer: TypeAlias = Callable[[str], str] + +DEFAULT_SIGNATURE: str +VERSION_PREFIX: str +BUNDLE_PREFIX: str +NEEDED: str +DEBUG: str +MINIFIED: str + +def set_resource_file_existence_checking(v: bool) -> None: ... +def set_auto_register_library(v: bool) -> None: ... + +class UnknownResourceExtensionError(Exception): ... +class ModeResourceDependencyError(Exception): ... + +UnknownResourceExtension = UnknownResourceExtensionError + +class UnknownResourceError(Exception): ... +class ConfigurationError(Exception): ... +class LibraryDependencyCycleError(Exception): ... +class SlotError(Exception): ... + +class Library: + path: str + name: str + rootpath: str + ignores: list[str] + version: str | None + known_resources: dict[str, Resource] + known_assets: dict[str, Asset] + module: ModuleType + compilers: dict[str, Compiler] + minifiers: dict[str, Minifier] + def __init__( + self, + name: str, + rootpath: str, + ignores: list[str] | None = None, + version: str | None = None, + compilers: dict[str, Compiler] | None = None, + minifiers: dict[str, Minifier] | None = None, + ) -> None: ... + def check_dependency_cycle(self, resource: Resource) -> None: ... + def register(self, resource: Resource) -> None: ... + def signature(self, recompute_hashes: bool = False, version_method: Callable[[str], str] | None = None) -> str: ... + +def caller_dir() -> str: ... + +class InclusionRenderers(dict[str, tuple[int, _Renderer]]): + def register(self, extension: str, renderer: _Renderer, order: int | None = None) -> None: ... + +inclusion_renderers: InclusionRenderers + +def register_inclusion_renderer(extension: str, renderer: _Renderer, order: int | None = None) -> None: ... +def render_ico(url: str) -> str: ... +def render_css(url: str) -> str: ... +def render_js(url: str) -> str: ... +def render_print_css(url: str) -> str: ... +def render_screen_css(url: str) -> str: ... + +class Renderable: + @abstractmethod + def render(self, library_url: str) -> str: ... + +class Dependable: + @property + @abstractmethod + def resources(self) -> set[Dependable]: ... + @property + @abstractmethod + def depends(self) -> set[Dependable]: ... + @property + @abstractmethod + def supports(self) -> set[Dependable]: ... + def add_dependency(self, dependency: Dependable) -> None: ... + @abstractmethod + def set_dependencies(self, dependencies: Iterable[Dependable] | None) -> None: ... + @abstractmethod + def list_assets(self) -> set[Asset]: ... + def list_supporting(self) -> set[Dependable]: ... + +class Asset(Dependable): + resources: set[Dependable] + depends: set[Dependable] + supports: set[Dependable] + library: Library + def __init__(self, library: Library, depends: Iterable[Dependable] | None = None) -> None: ... + def set_dependencies(self, depends: Iterable[Dependable] | None) -> None: ... + def list_assets(self) -> set[Asset]: ... + +_NothingType = NewType("_NothingType", object) +NOTHING: _NothingType + +class Resource(Renderable, Asset): + relpath: str + ext: str + mode_parent: str | None + compiler: Compiler + source: str | None + minifier: Minifier + minified: Resource | None + bottom: bool + dont_bundle: bool + renderer: _Renderer + modes: dict[str, Resource] + supersedes: list[Resource] + rollups: list[Resource] + def __init__( + self, + library: Library, + relpath: str, + depends: Iterable[Dependable] | None = None, + supersedes: list[Resource] | None = None, + bottom: bool = False, + renderer: _Renderer | None = None, + debug: str | Resource | None = None, + dont_bundle: bool = False, + minified: str | Resource | None = None, + minifier: Minifier | _NothingType = ..., + compiler: Compiler | _NothingType = ..., + source: str | None = None, + mode_parent: str | None = None, + ) -> None: ... + def fullpath(self, path: str | None = None) -> str: ... + def compile(self, force: bool = False) -> None: ... + def render(self, library_url: str) -> str: ... + def mode(self, mode: str | None) -> Resource: ... + def need(self, slots: dict[Slot, Resource] | None = None) -> None: ... + +_RequiredDefaultMarkerType = NewType("_RequiredDefaultMarkerType", object) +REQUIRED_DEFAULT_MARKER: _RequiredDefaultMarkerType + +class Slot(Asset): + default: Resource | None + ext: str + required: bool + def __init__( + self, + library: Library, + extension: str, + depends: Iterable[Dependable] | None = None, + required: bool | _RequiredDefaultMarkerType = ..., + default: Resource | None = None, + ) -> None: ... + +class FilledSlot(Renderable): + filledby: Resource + library: Library + relpath: str + bottom: bool + rollups: list[Resource] + dont_bundle: bool + ext: str + order: int + renderer: _Renderer + dependency_nr: int + modes: dict[str, FilledSlot] + def __init__(self, slot: Slot, resource: Resource) -> None: ... + def render(self, library_url: str) -> str: ... + def compile(self, force: bool = False) -> None: ... + def mode(self, mode: str | None) -> FilledSlot: ... + +class Group(Dependable): + resources: set[Dependable] + depends: set[Dependable] + supports: set[Dependable] + def __init__(self, depends: Iterable[Dependable]) -> None: ... + def set_dependencies(self, depends: Iterable[Dependable]) -> None: ... # type:ignore[override] + def list_assets(self) -> set[Asset]: ... + def need(self, slots: dict[Slot, Resource] | None = None) -> None: ... + +GroupResource = Group + +class NeededResources: + def __init__( + self, + versioning: bool = False, + versioning_use_md5: bool = False, + recompute_hashes: bool = True, + base_url: str | None = None, + script_name: str | None = None, + publisher_signature: str = ..., + resources: Iterable[Dependable] | None = None, + ) -> None: ... + def has_resources(self) -> bool: ... + def has_base_url(self) -> bool: ... + def set_base_url(self, url: str) -> None: ... + def need(self, resource: Resource | Group, slots: dict[Slot, Resource] | None = None) -> None: ... + def resources(self) -> set[Resource]: ... + def clear(self) -> None: ... + def library_url(self, library: Library) -> str: ... + +class DummyNeededResources: + def need(self, resource: Resource | Group, slots: dict[Slot, Resource] | None = None) -> None: ... + def has_resources(self) -> Literal[False]: ... + +thread_local_needed_data: local + +def init_needed( + versioning: bool = False, + versioning_use_md5: bool = False, + recompute_hashes: bool = True, + base_url: str | None = None, + script_name: str | None = None, + publisher_signature: str = ..., + resources: Iterable[Dependable] | None = None, +) -> NeededResources: ... +def del_needed() -> None: ... +def get_needed() -> NeededResources | DummyNeededResources: ... +def clear_needed() -> None: ... + +class Bundle(Renderable): + def __init__(self) -> None: ... + @property + def dirname(self) -> str: ... + @property + def library(self) -> Library: ... + @property + def renderer(self) -> _Renderer: ... + @property + def ext(self) -> str: ... + @property + def relpath(self) -> str: ... + def resources(self) -> list[Resource]: ... + def render(self, library_url: str) -> str: ... + def fits(self, resource: Resource) -> bool: ... + def append(self, resource: Resource) -> None: ... + def add_to_list(self, result: list[Renderable]) -> None: ... diff --git a/stubs/fanstatic/fanstatic/inclusion.pyi b/stubs/fanstatic/fanstatic/inclusion.pyi new file mode 100644 index 000000000000..53b0076396ef --- /dev/null +++ b/stubs/fanstatic/fanstatic/inclusion.pyi @@ -0,0 +1,22 @@ +from collections.abc import Iterable + +from fanstatic.core import Bundle, NeededResources, Resource + +def bundle_resources(resources: Iterable[Resource]) -> list[Resource | Bundle]: ... +def rollup_resources(resources: Iterable[Resource]) -> set[Resource]: ... +def sort_resources(resources: Iterable[Resource]) -> list[Resource]: ... + +class Inclusion: + needed: NeededResources + resources: list[Resource | Bundle] + def __init__( + self, + needed: NeededResources, + resources: Iterable[Resource] | None = None, + compile: bool = False, + bundle: bool = False, + mode: str | None = None, + rollup: bool = False, + ) -> None: ... + def __len__(self) -> int: ... + def render(self) -> str: ... diff --git a/stubs/fanstatic/fanstatic/injector.pyi b/stubs/fanstatic/fanstatic/injector.pyi new file mode 100644 index 000000000000..0b2fc5618802 --- /dev/null +++ b/stubs/fanstatic/fanstatic/injector.pyi @@ -0,0 +1,57 @@ +from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment +from abc import abstractmethod +from collections.abc import Iterable +from typing import Any +from typing_extensions import Literal, TypedDict + +from fanstatic.core import Dependable, NeededResources, Resource +from fanstatic.inclusion import Inclusion +from webob import Request, Response + +class _NeededResourcesConfig(TypedDict, total=False): + versioning: bool + versioning_use_md5: bool + recompute_hashes: bool + base_url: str | None + script_name: str | None + publisher_signature: str + resources: Iterable[Dependable] | None + +class _InjectorPluginOptions(TypedDict, total=False): + compile: bool + bundle: bool + rollup: bool + debug: bool + minified: bool + +class _TopBottomInjectorPluginOptions(_InjectorPluginOptions, total=False): + bottom: bool + force_bottom: bool + +CONTENT_TYPES: list[str] + +class Injector: + app: WSGIApplication + config: _NeededResourcesConfig + injector: InjectorPlugin + def __init__( + self, app: WSGIApplication, injector: InjectorPlugin | None = None, **config: Any + ) -> None: ... # FIXME: Switch to Unpack[_NeededResourcesConfig] + def __call__(self, environ: WSGIEnvironment, start_response: StartResponse) -> Iterable[bytes]: ... + +class InjectorPlugin: + @property + @abstractmethod + def name(self) -> str: ... + def __init__(self, options: _InjectorPluginOptions) -> None: ... + def make_inclusion(self, needed: NeededResources, resources: set[Resource] | None = None) -> Inclusion: ... + def __call__( + self, html: bytes, needed: NeededResources, request: Request | None = None, response: Response | None = None + ) -> None: ... + +class TopBottomInjector(InjectorPlugin): + name: Literal["topbottom"] + def __init__(self, options: _TopBottomInjectorPluginOptions) -> None: ... + def group(self, needed: NeededResources) -> tuple[Inclusion, Inclusion]: ... + +def make_injector(app: WSGIApplication, global_config: Any, **local_config: Any) -> Injector: ... diff --git a/stubs/fanstatic/fanstatic/publisher.pyi b/stubs/fanstatic/fanstatic/publisher.pyi new file mode 100644 index 000000000000..0e2ecf483e13 --- /dev/null +++ b/stubs/fanstatic/fanstatic/publisher.pyi @@ -0,0 +1,49 @@ +from _typeshed import StrOrBytesPath +from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment +from collections.abc import Iterable +from typing import IO, Any +from typing_extensions import Literal + +from fanstatic.core import Library +from fanstatic.registry import LibraryRegistry +from webob import Request, Response +from webob.dec import wsgify +from webob.static import DirectoryApp, FileApp + +MINUTE_IN_SECONDS: Literal[60] +HOUR_IN_SECONDS: Literal[3600] +DAY_IN_SECONDS: Literal[86400] +YEAR_IN_SECONDS: int +FOREVER: int + +class BundleApp(FileApp): + filenames: list[str] + def __init__(self, rootpath: str, bundle: IO[bytes], filenames: Iterable[StrOrBytesPath]) -> None: ... + @wsgify + def __call__(self, req: Request) -> Response: ... + +class LibraryPublisher(DirectoryApp): + ignores: list[str] + library: Library + cached_apps: dict[str, FileApp] + def __init__(self, library: Library) -> None: ... + @wsgify + def __call__(self, req: Request) -> Response: ... + +class Publisher: + registry: LibraryRegistry + directory_publishers: dict[str, LibraryPublisher] + def __init__(self, registry: LibraryRegistry) -> None: ... + @wsgify + def __call__(self, request: Request) -> Response: ... + +class Delegator: + app: WSGIApplication + publisher: Publisher + publisher_signature: str + trigger: str + def __init__(self, app: WSGIApplication, publisher: Publisher, publisher_signature: str = ...) -> None: ... + def is_resource(self, request: Request) -> bool: ... + def __call__(self, environ: WSGIEnvironment, start_response: StartResponse) -> Iterable[bytes]: ... + +def make_publisher(global_config: Any) -> Publisher: ... diff --git a/stubs/fanstatic/fanstatic/registry.pyi b/stubs/fanstatic/fanstatic/registry.pyi new file mode 100644 index 000000000000..3376e89c3da6 --- /dev/null +++ b/stubs/fanstatic/fanstatic/registry.pyi @@ -0,0 +1,45 @@ +from abc import abstractmethod +from collections.abc import Iterable +from threading import Lock +from typing import Any, ClassVar, Protocol, TypeVar +from typing_extensions import Literal, Self + +from fanstatic.compiler import Compiler, Minifier +from fanstatic.core import Library +from fanstatic.injector import InjectorPlugin +from pkg_resources import EntryPoint + +class _HasName(Protocol): + @property + def name(self) -> str: ... + +_NamedT = TypeVar("_NamedT", bound=_HasName) + +prepare_lock: Lock + +class Registry(dict[str, _NamedT]): + @property + @abstractmethod + def ENTRY_POINT(self) -> str: ... + def __init__(self, items: Iterable[_NamedT] = ()) -> None: ... # noqaY011 + def add(self, item: _NamedT) -> None: ... + def load_items_from_entry_points(self) -> None: ... + def make_item_from_entry_point(self, entry_point: EntryPoint) -> Any: ... + @classmethod + def instance(cls) -> Self: ... + +class LibraryRegistry(Registry[Library]): + ENTRY_POINT: ClassVar[Literal["fanstatic.libraries"]] + prepared: bool + def prepare(self) -> None: ... + +get_library_registry = LibraryRegistry.instance + +class CompilerRegistry(Registry[Compiler]): + ENTRY_POINT: ClassVar[Literal["fanstatic.compilers"]] + +class MinifierRegistry(Registry[Minifier]): + ENTRY_POINT: ClassVar[Literal["fanstatic.minifiers"]] + +class InjectorRegistry(Registry[InjectorPlugin]): + ENTRY_POINT: ClassVar[Literal["fanstatic.injectors"]] diff --git a/stubs/fanstatic/fanstatic/wsgi.pyi b/stubs/fanstatic/fanstatic/wsgi.pyi new file mode 100644 index 000000000000..99d63abea818 --- /dev/null +++ b/stubs/fanstatic/fanstatic/wsgi.pyi @@ -0,0 +1,22 @@ +from _typeshed.wsgi import WSGIApplication +from typing import Any + +from fanstatic.core import Resource +from fanstatic.injector import InjectorPlugin +from fanstatic.publisher import Delegator +from webob import Request, Response +from webob.dec import wsgify + +def Fanstatic( + app: WSGIApplication, publisher_signature: str = ..., injector: InjectorPlugin | None = None, **config: Any +) -> Delegator: ... +def make_fanstatic(app: WSGIApplication, global_config: Any, **local_config: Any) -> Delegator: ... + +class Serf: + resource: Resource + def __init__(self, resource: Resource) -> None: ... + @wsgify + def __call__(self, request: Request) -> Response: ... + +def make_serf(global_config: Any, **local_config: Any) -> Serf: ... +def resolve(name: str, module: str | None = None) -> Any: ...