Typing: add stubs, hints and tooling settings

This commit is contained in:
Evilham 2023-12-06 14:37:23 +01:00
parent ba98f0c389
commit e5cfceda3a
Signed by: evilham
GPG key ID: AE3EE30D970886BF
41 changed files with 2723 additions and 91 deletions

View file

@ -22,6 +22,7 @@ mypy = "*"
isort = "*"
types-markdown = "*"
types-pyyaml = "*"
mypy-zope = "*"
[pipenv]
allow_prereleases = true

139
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "7a1e05f686d4492800c29d951c4ec9c8fa9e37b01bffa4acc80a28de398ea4c7"
"sha256": "1e70e3d45c60ddbd24fc61b5f1a2fbb8c8fef62391c7af2e89893a560c1831fc"
},
"pipfile-spec": 6,
"requires": {
@ -44,6 +44,7 @@
"sha256:12611c4b0a8b1c461646228344784a1089bc0c49975680a2f54f516e71e9b79e",
"sha256:12f40f78dcba4aa7d1354d35acf45fae9488862a4fb695c7eeda5ace6aae273f",
"sha256:14d41933510717f98aac63378b7956bbe548986e435df173c841d7f2bd0b2de7",
"sha256:196008d91201bbb1aa4e666fee5e610face25d532e433a560cabb33bfdff958b",
"sha256:24c2ebd287b5b11016f31d506ca1052d068c3f9dc817160628504690376ff050",
"sha256:2ade10e8613a3b8446214846d3ddbd56cfe9205a7d64742f0b75458c868f7492",
"sha256:2e197534c884336f9020c1f3a8efbaab0aa96fc798068cb2da9c671818b7fbb0",
@ -172,11 +173,11 @@
},
"importlib-metadata": {
"hashes": [
"sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb",
"sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"
"sha256:7fc841f8b8332803464e5dc1c63a2e59121f46ca186c0e2e182e80bf8c1319f7",
"sha256:d97503976bb81f40a193d41ee6570868479c69d5068651eb039c40d850c59d67"
],
"markers": "python_version < '3.10'",
"version": "==6.8.0"
"version": "==7.0.0"
},
"incremental": {
"hashes": [
@ -538,37 +539,37 @@
},
"mypy": {
"hashes": [
"sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340",
"sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49",
"sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82",
"sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce",
"sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb",
"sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51",
"sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5",
"sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e",
"sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7",
"sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33",
"sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9",
"sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1",
"sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6",
"sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a",
"sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe",
"sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7",
"sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200",
"sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7",
"sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a",
"sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28",
"sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea",
"sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120",
"sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d",
"sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42",
"sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea",
"sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2",
"sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"
"sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315",
"sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0",
"sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373",
"sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a",
"sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161",
"sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275",
"sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693",
"sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb",
"sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65",
"sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4",
"sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb",
"sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243",
"sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14",
"sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4",
"sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1",
"sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a",
"sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160",
"sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25",
"sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12",
"sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d",
"sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92",
"sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770",
"sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2",
"sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70",
"sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb",
"sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5",
"sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.7.1"
"version": "==1.5.1"
},
"mypy-extensions": {
"hashes": [
@ -578,6 +579,14 @@
"markers": "python_version >= '3.5'",
"version": "==1.0.0"
},
"mypy-zope": {
"hashes": [
"sha256:003953896629d762d7f497135171ad549df42a8ac63c1521a230832dd6f7fc25",
"sha256:ffa291a7af9f5904ce9f0e56de44323a4476e28aaf0d68361b62b1b0e997d0b8"
],
"index": "pypi",
"version": "==1.0.1"
},
"pbr": {
"hashes": [
"sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda",
@ -675,6 +684,14 @@
"markers": "python_full_version >= '3.7.0'",
"version": "==13.7.0"
},
"setuptools": {
"hashes": [
"sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2",
"sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"
],
"markers": "python_version >= '3.8'",
"version": "==69.0.2"
},
"smmap": {
"hashes": [
"sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62",
@ -723,6 +740,64 @@
],
"markers": "python_version >= '3.8'",
"version": "==4.9.0rc1"
},
"zope.event": {
"hashes": [
"sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26",
"sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"
],
"markers": "python_version >= '3.7'",
"version": "==5.0"
},
"zope.interface": {
"hashes": [
"sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff",
"sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c",
"sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac",
"sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f",
"sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d",
"sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309",
"sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736",
"sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179",
"sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb",
"sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941",
"sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d",
"sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92",
"sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b",
"sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41",
"sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f",
"sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3",
"sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d",
"sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8",
"sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3",
"sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1",
"sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1",
"sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40",
"sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d",
"sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1",
"sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605",
"sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7",
"sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd",
"sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43",
"sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0",
"sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b",
"sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379",
"sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a",
"sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83",
"sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56",
"sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9",
"sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"
],
"markers": "python_version >= '3.7'",
"version": "==6.1"
},
"zope.schema": {
"hashes": [
"sha256:cf006c678793b00e0075ad54d55281c8785ea21e5bc1f5ec0584787719c2aab2",
"sha256:ead4dbcb03354d4e410c9a3b904451eb44d90254751b1cbdedf4a61aede9fbb9"
],
"markers": "python_version >= '3.7'",
"version": "==7.0.1"
}
}
}

14
mypy.ini Normal file
View file

@ -0,0 +1,14 @@
[mypy]
namespace_packages=True
plugins=mypy_zope:plugin
#TODO: progress so we can enable this
#strict=True
check_untyped_defs=True
disallow_untyped_defs=True
warn_redundant_casts=True
warn_unused_configs= True
warn_unused_ignores = False
warn_no_return=True
warn_return_any=True
warn_unreachable=True
enable_error_code=unused-awaitable

17
pyproject.toml Normal file
View file

@ -0,0 +1,17 @@
[tool.black]
target-version = ['py39']
[tool.isort]
profile = "black"
sections = "FUTURE,STDLIB,ZOPE,OPENSSL,THIRDPARTY,FIRSTPARTY,LOCALFOLDER"
default_section = "THIRDPARTY"
no_lines_before = "LOCALFOLDER"
known_first_party = "adlermanager"
known_zope = "zope"
known_openssl = "OpenSSL"
combine_as_imports = true
#skip = "src/twisted/logger/__init__.py,src/twisted/internet/reactor.py"
[tool.pyright]
include = ["src"]
reportIncompatibleMethodOverride = false

9
setup.cfg Normal file
View file

@ -0,0 +1,9 @@
[flake8]
profile = "black"
max-line-length = 88
extend-ignore = E203
statistics = True
[options]
package_dir = =src
packages = find:

View file

@ -11,7 +11,7 @@ if TYPE_CHECKING:
class AdlerManagerSSHProtocol(SSHSimpleProtocol):
sites_manager: "SitesManager"
def __init__(self, user: SSHSimpleAvatar, interactive: bool = True):
def __init__(self, user: SSHSimpleAvatar, interactive: bool = True) -> None:
"""
Create an instance of AdlerManagerSSHProtocol.
"""
@ -19,7 +19,7 @@ class AdlerManagerSSHProtocol(SSHSimpleProtocol):
# TODO: Do stuff like getting user sites, showing alert warnings, etc.
def do_tmp_dump_state(self):
def do_tmp_dump_state(self) -> None:
"""
This command is temporary and just dumps all known state.
"""
@ -32,6 +32,6 @@ class AdlerManagerSSHProtocol(SSHSimpleProtocol):
self.terminal.nextLine()
@functools.lru_cache() # we don't need to re-read every time
def motd(self):
def motd(self) -> str:
# TODO: Use data location?
return open("motd.txt").read()

View file

@ -25,7 +25,7 @@ class AdlerManagerTokenResource(TokenResource):
"""
TokenResource.__init__(self, tokens=sites_manager.tokens)
def preprocess_header(self, header: str):
def preprocess_header(self, header: str) -> str:
return header.split(" ")[-1]
def processToken(self, token_data: "SiteManager", request: Request) -> int:

View file

@ -22,7 +22,7 @@ class IncidentManager(object):
_alert_timeouts: Dict[str, defer.Deferred[None]] = attr.ib(factory=dict)
"""alert_label -> timeout"""
_monitoring_down = attr.ib(default=False)
_monitoring_down: bool = attr.ib(default=False)
# _logs = attr.ib(factory=list)
# TODO: Get IncidentClosing timeout from settings?
@ -31,7 +31,7 @@ class IncidentManager(object):
# Defaulting to 5m as alertmanager
_alert_resolve_timeout: int = attr.ib(default=5 * 60)
def process_heartbeats(self, heartbeats: Iterable[Alert], timestamp: str):
def process_heartbeats(self, heartbeats: Iterable[Alert], timestamp: str) -> None:
if heartbeats:
self.last_updated = timestamp
if self._monitoring_down:
@ -42,7 +42,7 @@ class IncidentManager(object):
)
self.log_event("[Meta]MonitoringUp", timestamp)
def process_alerts(self, alerts: Iterable[Alert], timestamp: str):
def process_alerts(self, alerts: Iterable[Alert], timestamp: str) -> None:
if alerts:
self._timeout.cancel()
self._timeout = task.deferLater(

View file

@ -1,4 +1,4 @@
from typing import Any, Dict, Generator, List, cast
from typing import Any, Dict, Generator, List, Optional, cast
import attr
import yaml
@ -38,17 +38,17 @@ class SiteManager(object):
path: FilePath = attr.ib()
tokens: List[str] = attr.ib(factory=list)
log = Logger()
monitoring_is_down = attr.ib(default=False)
monitoring_is_down: bool = attr.ib(default=False)
definition: Dict[str, Any] = attr.ib(factory=dict)
title = attr.ib(default="")
title: str = attr.ib(default="")
_timeout: defer.Deferred[None] = attr.ib(factory=noop_deferred)
site_name = attr.ib(default="")
site_name: str = attr.ib(default="")
# TODO: Get monitoring timeout from config
# Default to 2 mins
_timeout_seconds = 2 * 60
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
self.load_definition()
self.title = self.definition["title"]
self.load_tokens()
@ -67,11 +67,11 @@ class SiteManager(object):
for manager in self.service_managers:
manager.monitoring_down(self.last_updated.getStr())
def load_definition(self):
def load_definition(self) -> None:
with self.path.child("site.yml").open("r") as f:
self.definition = yaml.safe_load(f)
def load_tokens(self):
def load_tokens(self) -> None:
tokens_file = self.path.child("tokens.txt")
if tokens_file.exists():
with open(tokens_file.path, "r") as f:
@ -82,7 +82,7 @@ class SiteManager(object):
"your site will never update".format(self.title)
)
def process_alerts(self, raw_alerts: List[Dict[str, Any]]):
def process_alerts(self, raw_alerts: List[Dict[str, Any]]) -> None:
self.last_updated.now()
self.monitoring_is_down = False
@ -112,7 +112,7 @@ class SiteManager(object):
manager.process_alerts(filtered_alerts, timestamp)
@property
def status(self):
def status(self) -> Severity:
if self.monitoring_is_down:
return Severity.ERROR
return max(
@ -124,13 +124,13 @@ class SiteManager(object):
class ServiceManager(object):
path: FilePath = attr.ib()
definition: Dict[str, Any] = attr.ib()
current_incident = attr.ib(default=None)
current_incident: Optional[IncidentManager] = attr.ib(default=None)
component_labels: List[str] = attr.ib(factory=list)
label = attr.ib(default="")
label: str = attr.ib(default="")
log = Logger()
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
self.label = self.definition["label"]
# TODO: Recover status after server restart
self.component_labels = [
@ -144,11 +144,11 @@ class ServiceManager(object):
if self.current_incident:
self.current_incident.monitoring_down(timestamp)
def process_heartbeats(self, heartbeats: List[Alert], timestamp: str):
def process_heartbeats(self, heartbeats: List[Alert], timestamp: str) -> None:
if self.current_incident:
self.current_incident.process_heartbeats(heartbeats, timestamp)
def process_alerts(self, alerts: List[Alert], timestamp: str):
def process_alerts(self, alerts: List[Alert], timestamp: str) -> None:
# Filter by service-affecting alerts
alerts = [
alert
@ -172,7 +172,7 @@ class ServiceManager(object):
self.current_incident = None
@property
def status(self):
def status(self) -> Severity:
if self.current_incident:
# TODO: Consistent naming
return max(

View file

@ -1,4 +1,4 @@
from typing import Any, Dict
from typing import Any, Dict, Generator, Union, cast
from twisted.internet import defer
from twisted.web import resource, server
@ -26,7 +26,7 @@ class TokenResource(resource.Resource):
resource.Resource.__init__(self)
self.tokens = tokens
def render(self, request: Request):
def render(self, request: Request) -> Union[bytes, int]:
"""
See L{resource.Resource}.
@ -41,7 +41,9 @@ class TokenResource(resource.Resource):
if token_data is None:
return self._unauthorized(request)
self._processToken(token_data, request).addCallback(lambda x: request.finish())
self._processToken(token_data, request).addCallback( # type: ignore
lambda x: request.finish()
)
return server.NOT_DONE_YET
def preprocess_header(self, header: str) -> str:
@ -63,7 +65,9 @@ class TokenResource(resource.Resource):
return OK
@defer.inlineCallbacks
def _processToken(self, token_data: Any, request: Request):
def _processToken(
self, token_data: Any, request: Request
) -> Generator[defer.Deferred[int], None, None]:
"""
Invoke L{TokenResource.processToken} and produce a 500 if it fails.
@ -71,9 +75,8 @@ class TokenResource(resource.Resource):
@param request: The request object associated to this request.
"""
try:
code: int = yield defer.maybeDeferred(
self.processToken, token_data, request
)
res = yield defer.maybeDeferred(self.processToken, token_data, request)
code: int = cast(int, res)
except Exception:
import traceback
@ -82,7 +85,7 @@ class TokenResource(resource.Resource):
request.setResponseCode(code) # type: ignore
defer.returnValue(code)
def _unauthorized(self, request: Request):
def _unauthorized(self, request: Request) -> bytes:
"""
Send a 401 Unauthorized response.
@ -91,7 +94,7 @@ class TokenResource(resource.Resource):
request.setResponseCode(UNAUTHORIZED) # type: ignore
return self.unauthorizedPage().render(request) # type: ignore
def unauthorizedPage(self):
def unauthorizedPage(self) -> resource.ErrorPage:
"""
Page to render when there is no valid token.
This makes use of L{TokenResource.unauthorizedMessage} by default.
@ -100,13 +103,13 @@ class TokenResource(resource.Resource):
UNAUTHORIZED, "Unauthorized", self.unauthorizedMessage()
)
def unauthorizedMessage(self):
def unauthorizedMessage(self) -> str:
"""
Message to show when there is no valid token.
"""
return "Pass a valid token in the {} header.".format(self.HEADER)
def getChild(self, name: str, request: Request):
def getChild(self, name: str, request: Request) -> "TokenResource":
"""
Use this child for everything but the explicitly overriden.

View file

@ -4,6 +4,7 @@ from typing import cast
import jinja2
import markdown
from klein import Klein
from klein.resource import KleinResource
from twisted.logger import Logger
from twisted.python.filepath import FilePath
from twisted.web import resource, static
@ -14,7 +15,7 @@ from .Config import Config
from .SitesManager import SiteManager, SitesManager
def get_jinja_env(supportDir: str):
def get_jinja_env(supportDir: str) -> jinja2.Environment:
"""
Return a L{jinja2.Environment} with templates loaded from:
- Package
@ -46,7 +47,7 @@ def get_jinja_env(supportDir: str):
return templates
def web_root(sites_manager: "SitesManager"):
def web_root(sites_manager: "SitesManager") -> KleinResource:
app = Klein()
log = Logger()

View file

@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, Optional, Tuple, Union, cast
from typing import Any, Callable, Dict, Iterator, Optional, Tuple, Union, cast
from zope.interface import implementer
@ -14,6 +14,7 @@ from twisted.conch.insults import insults
from twisted.conch.manhole_tap import chainedProtocolFactory
from twisted.conch.ssh import keys, session
from twisted.cred import portal
from twisted.internet.defer import Deferred
from twisted.internet.error import ProcessTerminated
from twisted.python import failure, filepath
@ -23,7 +24,7 @@ class SSHSimpleProtocol(recvline.HistoricRecvLine):
keyHandlers: Dict[bytes, Callable[[], None]]
interactive: bool = True
def __init__(self, user: "SSHSimpleAvatar", interactive: bool = True):
def __init__(self, user: "SSHSimpleAvatar", interactive: bool = True) -> None:
recvline.HistoricRecvLine.__init__(self)
self.user = user
self.interactive = interactive
@ -35,7 +36,7 @@ class SSHSimpleProtocol(recvline.HistoricRecvLine):
"""
return self.terminal.write(msg) # type: ignore
def connectionMade(self):
def connectionMade(self) -> None:
recvline.HistoricRecvLine.connectionMade(self)
if not self.interactive:
return
@ -49,16 +50,16 @@ class SSHSimpleProtocol(recvline.HistoricRecvLine):
self.showPrompt()
def handle_EOF(self):
def handle_EOF(self) -> None:
if self.lineBuffer: # type: ignore
self.terminal_write(b"\a")
else:
self.exitWithCode(0)
def handle_QUIT(self):
def handle_QUIT(self) -> None:
self.terminal.loseConnection()
def showPrompt(self):
def showPrompt(self) -> None:
if self.interactive:
self.terminal_write(">>> ")
@ -109,9 +110,9 @@ class SSHSimpleProtocol(recvline.HistoricRecvLine):
failure.Failure(ProcessTerminated(code, None, None))
)
def do_help(self, cmd: bytes = b""):
def do_help(self, cmd: bytes = b"") -> None:
"""
Get help on a command. Usage: help command
Get help on a command. Usage: help [command]
"""
if cmd:
func = self._getCommand(cmd)
@ -130,26 +131,26 @@ class SSHSimpleProtocol(recvline.HistoricRecvLine):
self.terminal.nextLine()
self.terminal.nextLine()
def do_whoami(self):
def do_whoami(self) -> None:
"""
Prints your username. Usage: whoami
"""
self.terminal_write(self.user.username)
self.terminal.nextLine()
def do_clear(self):
def do_clear(self) -> None:
"""
Clears the screen. Usage: clear
"""
self.terminal.reset()
def do_exit(self):
def do_exit(self) -> None:
"""
Exit session. Usage: exit
"""
self.exitWithCode(0)
def motd(self):
def motd(self) -> str:
return ""
@ -162,12 +163,12 @@ class SSHSimpleAvatar(avatar.ConchUser):
self.proto = proto
self.channelLookup.update({b"session": session.SSHSession}) # type: ignore
def openShell(self, protocol: session.SSHSessionProcessProtocol):
def openShell(self, protocol: session.SSHSessionProcessProtocol) -> None:
serverProtocol = insults.ServerProtocol(self.proto, self)
serverProtocol.makeConnection(protocol) # type: ignore
protocol.makeConnection(session.wrapProtocol(serverProtocol)) # type: ignore
def getPty(self, terminal, windowSize, attrs): # type: ignore
def getPty(self, terminal, windowSize, attrs) -> None: # type: ignore
return None
def execCommand(
@ -188,7 +189,16 @@ class SSHSimpleAvatar(avatar.ConchUser):
"""
pass
def closed(self):
def eofReceived(self) -> None:
"""
Called when the other side has indicated no more data will be sent.
"""
pass
def closed(self) -> None:
"""
Called when the session is closed.
"""
pass
@ -211,14 +221,19 @@ class SSHSimpleRealm:
"""
self.proto = proto
def requestAvatar(self, avatarId: bytes, mind: Any, *interfaces: Any):
def requestAvatar(
self,
avatarId: Union[bytes, Tuple[()]],
mind: object,
*interfaces: portal._InterfaceItself, # type: ignore
) -> Union[Deferred[portal._requestResult], portal._requestResult]: # type: ignore
"""
Return a L{SSHSimpleAvatar} that uses ``self.proto`` as protocol.
@see: L{portal.IRealm}
"""
if conchinterfaces.IConchUser in interfaces:
avatar = SSHSimpleAvatar(avatarId, self.proto)
avatar = SSHSimpleAvatar(cast(bytes, avatarId), self.proto)
return interfaces[0], avatar, lambda: None
else:
raise Exception("No supported interfaces found.")
@ -251,7 +266,7 @@ class SSHKeyDirectory(object):
self.baseDir = baseDir
self.parseKey = parseKey
def getAuthorizedKeys(self, username: bytes):
def getAuthorizedKeys(self, username: bytes) -> Iterator[keys.Key]:
keyFile = self.baseDir.child(username + b".key")
keyDir = self.baseDir.child(username)

View file

@ -31,11 +31,11 @@ class Severity(IntEnum):
return Severity.from_string(alert.labels.get("severity", "OK"))
@property
def css(self):
def css(self) -> str:
classes = {self.OK: "success", self.WARNING: "warning", self.ERROR: "danger"}
return classes[self]
def __str__(self):
def __str__(self) -> str:
return self.css

View file

@ -16,21 +16,21 @@ class TimestampFile(object):
with self.path.open("w") as f:
f.write(time.strftime(_blessed_date_format).encode("utf-8"))
def now(self):
def now(self) -> None:
self.set(current_time())
def getStr(self):
def getStr(self) -> str:
if not self.path.exists():
return ""
with self.path.open("r") as f:
return f.read().decode("utf-8")
def current_time():
def current_time() -> datetime:
return datetime.now(timezone.utc)
def current_timestamp():
def current_timestamp() -> str:
return current_time().strftime(_blessed_date_format)

View file

@ -0,0 +1,61 @@
"""
This type stub file was generated by pyright.
"""
from typing import TYPE_CHECKING
from ._app import (
Klein,
KleinErrorHandler,
KleinRenderable,
KleinRouteHandler,
handle_errors,
route,
run,
subroute,
url_for,
urlFor,
)
from ._dihttp import RequestComponent, RequestURL, Response
from ._form import Field, FieldValues, Form, RenderableForm
from ._plating import Plating
from ._requirer import Requirer
from ._session import Authorization, SessionProcurer
from ._version import __version__ as _incremental_version
from .resource import _SpecialModuleObject
if TYPE_CHECKING:
resource = ...
else: ...
__all__ = (
"Klein",
"KleinErrorHandler",
"KleinRenderable",
"KleinRouteHandler",
"Plating",
"Field",
"FieldValues",
"Form",
"RequestComponent",
"RequestURL",
"Response",
"RenderableForm",
"SessionProcurer",
"Authorization",
"Requirer",
"__author__",
"__copyright__",
"__license__",
"__version__",
"handle_errors",
"resource",
"route",
"run",
"subroute",
"urlFor",
"url_for",
)
__version__ = ...
__author__ = ...
__license__ = ...
__copyright__ = ...

315
typings/klein/_app.pyi Normal file
View file

@ -0,0 +1,315 @@
"""
This type stub file was generated by pyright.
"""
from contextlib import contextmanager
from typing import (
IO,
Any,
Awaitable,
Callable,
Dict,
Iterator,
List,
Mapping,
Optional,
Tuple,
Type,
Union,
overload,
)
from zope.interface import implementer
from twisted.python.failure import Failure
from twisted.web.iweb import IRenderable, IRequest
from twisted.web.resource import IResource
from twisted.web.server import Request
from werkzeug.routing import Map, MapAdapter
from ._interfaces import IKleinRequest, KleinQueryValue
from ._resource import KleinResource
"""
Applications are great. Lets have more of them.
"""
KleinSynchronousRenderable = Union[str, bytes, IResource, IRenderable]
KleinRenderable = Union[
KleinSynchronousRenderable, Awaitable[KleinSynchronousRenderable]
]
class KleinRouteFunction(Protocol):
def __call__(_self, request: IRequest) -> KleinRenderable:
"""
Function that, when decorated by L{Klein.route}, handles a Klein
request.
"""
...
class KleinRouteMethod(Protocol):
def __call__(_self, self: Any, request: IRequest) -> KleinRenderable:
"""
Method that, when decorated by L{Klein.route}, handles a Klein
request.
"""
...
class KleinErrorFunction(Protocol):
def __call__(_self, request: IRequest, failure: Failure) -> KleinRenderable:
"""
Function that, when registered with L{Klein.handle_errors}, handles
errors raised during request routing.
"""
...
class KleinErrorMethod(Protocol):
def __call__(
_self, self: Optional[Klein], request: IRequest, failure: Failure
) -> KleinRenderable:
"""
Method that, when registered with L{Klein.handle_errors}, handles
errors raised during request routing.
"""
...
KleinRouteHandler = Union[KleinRouteFunction, KleinRouteMethod]
KleinErrorHandler = Union[KleinErrorFunction, KleinErrorMethod]
def buildURL(
mapper: MapAdapter,
endpoint: str,
values: Optional[Mapping[str, KleinQueryValue]] = ...,
method: Optional[str] = ...,
force_external: bool = ...,
append_unknown: bool = ...,
) -> str: ...
@implementer(IKleinRequest)
class KleinRequest:
def __init__(self, request: Request) -> None: ...
def url_for(
self,
endpoint: str,
values: Optional[Mapping[str, KleinQueryValue]] = ...,
method: Optional[str] = ...,
force_external: bool = ...,
append_unknown: bool = ...,
) -> str: ...
ErrorHandlers = List[Tuple[List[Type[Exception]], KleinErrorHandler]]
class Klein:
"""
L{Klein} is an object which is responsible for maintaining the routing
configuration of our application.
@ivar _url_map: A C{werkzeug.routing.Map} object which will be used for
routing resolution.
@ivar _endpoints: A C{dict} mapping endpoint names to handler functions.
"""
_subroute_segments = ...
def __init__(self) -> None: ...
def __eq__(self, other: Any) -> bool: ...
def __ne__(self, other: Any) -> bool: ...
@property
def url_map(self) -> Map:
"""
Read only property exposing L{Klein._url_map}.
"""
...
@property
def endpoints(self) -> Dict[str, KleinRouteHandler]:
"""
Read only property exposing L{Klein._endpoints}.
"""
...
def execute_endpoint(
self, endpoint: str, request: IRequest, *args: Any, **kwargs: Any
) -> KleinRenderable:
"""
Execute the named endpoint with all arguments and possibly a bound
instance.
"""
...
def execute_error_handler(
self, handler: KleinErrorMethod, request: IRequest, failure: Failure
) -> KleinRenderable:
"""
Execute the passed error handler, possibly with a bound instance.
"""
...
def resource(self) -> KleinResource:
"""
Return an L{IResource} which suitably wraps this app.
@returns: An L{IResource}
"""
...
def __get__(self, instance: Any, owner: object) -> Klein:
"""
Get an instance of L{Klein} bound to C{instance}.
"""
...
def route(
self, url: str, *args: Any, **kwargs: Any
) -> Callable[[KleinRouteHandler], KleinRouteHandler]:
"""
Add a new handler for C{url} passing C{args} and C{kwargs} directly to
C{werkzeug.routing.Rule}. The handler function will be passed at least
one argument an L{twisted.web.server.Request} and any keyword arguments
taken from the C{url} pattern.
::
@app.route("/")
def index(request):
return "Hello"
@param url: A werkzeug URL pattern given to C{werkzeug.routing.Rule}.
@param branch: A bool indiciated if a branch endpoint should
be added that allows all child path segments that don't
match some other route to be consumed. Default C{False}.
@returns: decorated handler function.
"""
...
@contextmanager
def subroute(self, prefix: str) -> Iterator[Klein]:
"""
Within this block, C{@route} adds rules to a
C{werkzeug.routing.Submount}.
This is implemented by tinkering with the instance's C{_url_map}
variable. A context manager allows us to gracefully use the pattern of
"change a variable, do some things with the new value, then put it back
to how it was before.
Named "subroute" to try and give callers a better idea of its
relationship to C{@route}.
Usage:
::
with app.subroute("/prefix") as app:
@app.route("/foo")
def foo_handler(request):
return 'I respond to /prefix/foo'
@param prefix: The string that will be prepended to the paths of all
routes established during the with-block.
"""
class SubmountMap: ...
@overload
def handle_errors(
self, f_or_exception: KleinErrorHandler, *additional_exceptions: Type[Exception]
) -> Callable[[KleinErrorHandler], Callable]: ...
@overload
def handle_errors(
self, f_or_exception: Type[Exception], *additional_exceptions: Type[Exception]
) -> Callable[[KleinErrorHandler], Callable]: ...
def handle_errors(
self,
f_or_exception: Union[KleinErrorHandler, Type[Exception]],
*additional_exceptions: Type[Exception],
) -> Callable[[KleinErrorHandler], Callable]:
"""
Register an error handler. This decorator supports two syntaxes. The
simpler of these can be used to register a handler for all C{Exception}
types::
@app.handle_errors
def error_handler(request, failure):
request.setResponseCode(500)
return 'Uh oh'
Alternately, a handler can be registered for one or more specific
C{Exception} types::
@app.handle_errors(EncodingError, ValidationError):
def error_handler(request, failure)
request.setResponseCode(400)
return failure.getTraceback()
The handler will be passed a L{twisted.web.server.Request} as well as a
L{twisted.python.failure.Failure} instance. Error handlers may return a
deferred, a failure or a response body.
If more than one error handler is registered, the handlers will be
executed in the order in which they are defined, until a handler is
encountered which completes successfully. If no handler completes
successfully, L{twisted.web.server.Request}'s processingFailed() method
will be called.
In addition to handling errors that occur within a L{KleinRouteHandler},
error handlers also handle any L{werkzeug.exceptions.HTTPException}
which is raised during request routing.
In particular, C{werkzeug.exceptions.NotFound} will be raised if no
matching route is found, so to return a custom 404 users can do the
following::
@app.handle_errors(NotFound)
def error_handler(request, failure):
request.setResponseCode(404)
return 'Not found'
@param f_or_exception: An error handler function, or an C{Exception}
subclass to scope the decorated handler to.
@param additional_exceptions: Additional C{Exception} subclasses to
scope the decorated function to.
@returns: decorated error handler function.
"""
...
def urlFor(
self,
request: IRequest,
endpoint: str,
values: Optional[Mapping[str, KleinQueryValue]] = ...,
method: Optional[str] = ...,
force_external: bool = ...,
append_unknown: bool = ...,
) -> str: ...
url_for = ...
def run(
self,
host: Optional[str] = ...,
port: Optional[int] = ...,
logFile: Optional[IO] = ...,
endpoint_description: Optional[str] = ...,
displayTracebacks: bool = ...,
) -> None:
"""
Run a minimal twisted.web server on the specified C{port}, bound to the
interface specified by C{host} and logging to C{logFile}.
This function will run the default reactor for your platform and so
will block the main thread of your application. It should be the last
thing your klein application does.
@param host: The hostname or IP address to bind the listening socket
to. "0.0.0.0" will allow you to listen on all interfaces, and
"127.0.0.1" will allow you to listen on just the loopback
interface.
@param port: The TCP port to accept HTTP requests on.
@param logFile: The file object to log to, by default C{sys.stdout}
@param endpoint_description: specification of endpoint. Must contain
protocol, port and interface. May contain other optional arguments,
e.g. to use SSL: "ssl:443:privateKey=key.pem:certKey=crt.pem"
@param displayTracebacks: Weather a processing error will result in
a page displaying the traceback with debugging information or not.
"""
...
_globalKleinApp = ...
route = ...
run = ...
subroute = ...
resource = ...
handle_errors = ...
urlFor = ...

View file

@ -0,0 +1,64 @@
"""
This type stub file was generated by pyright.
"""
from typing import Callable, Optional, TypeVar
C = TypeVar("C", bound=Callable)
def bindable(bindable: C) -> C:
"""
Mark a method as a "bindable" method.
If a L{Klein.app} resource is found on an instance object (i.e. is returned
from C{YourObject().app.resource()}), it will pass C{self} from that
instance to all of its routes, making a signature of 2 arguments: C{self}
and C{request} However, if it's found globally (i.e. C{app = Klein()};
C{@app.route(...)} at global scope), then it will only receive one:
C{request}. However, for decorators that must be able to live between
C{@route} and the user's function, but still need to manipulate the
C{request} object, they need to be invoked with a consistent argument
signature. A method decorated with C{@bindable} will therefore always take
C{instance, request} as its first two arguments, even if C{instance} is
C{None} when the L{Klein} object is not bound to an instance.
@return: its argument, modified to mark it as unconditionally requiring an
instance argument.
"""
...
def modified(
modification: str, original: Callable, modifier: Optional[Callable] = ...
) -> Callable:
"""
Annotate a callable as a modified wrapper of an original callable.
@param modification: A name for the type of modification, for example "form
processor" or "request forwarder"; this will be tacked on to the name
of the resulting function.
@param modifier: Another decorator which, if given, will be applied to the
function that decorates this function. Additionally, I{any new
attributes} set on the decorated function by C{modifier} will be
I{copied to} C{original}. This allows attributes set by "inner"
decorators such as L{klein.Form.handler} and L{klein.app.Klein.route}
to set attributes that will be visible at the top level.
@return: A new callable; this may have a different argument signature or
return value, and is only related to C{original} in the sense that it
likely calls it.
"""
...
def named(name: str) -> Callable[[C], C]:
"""
Change the name of a function to the given name.
"""
...
def originalName(function: Callable) -> str:
"""
Get the original, user-specified name of C{function}, chasing back any
wrappers applied with C{modified}.
"""
...

90
typings/klein/_dihttp.pyi Normal file
View file

@ -0,0 +1,90 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, Dict, Mapping, Sequence, Type, Union
from zope.interface import Interface, implementer, provider
import attr
from hyperlink import DecodedURL
from twisted.python.components import Componentized
from twisted.web.iweb import IRequest
from .interfaces import IDependencyInjector, IRequestLifecycle, IRequiredParameter
"""
Dependency-Injected HTTP metadata.
"""
def urlFromRequest(request: IRequest) -> DecodedURL: ...
@provider(IRequiredParameter, IDependencyInjector)
class RequestURL:
"""
Require a hyperlink L{DecodedURL} object from a L{Requirer}.
@since: Klein NEXT
"""
@classmethod
def registerInjector(
cls,
injectionComponents: Componentized,
parameterName: str,
requestLifecycle: IRequestLifecycle,
) -> IDependencyInjector: ...
@classmethod
def injectValue(
cls, instance: Any, request: IRequest, routeParams: Dict[str, Any]
) -> DecodedURL: ...
@classmethod
def finalize(cls) -> None:
"Nothing to do upon finalization."
...
@implementer(IRequiredParameter, IDependencyInjector)
@attr.frozen
class RequestComponent:
"""
Require a hyperlink L{DecodedURL} object from a L{Requirer}.
@since: Klein NEXT
"""
interface: Type[Interface]
def registerInjector(
self,
injectionComponents: Componentized,
parameterName: str,
requestLifecycle: IRequestLifecycle,
) -> IDependencyInjector: ...
def injectValue(
self, instance: Any, request: IRequest, routeParams: Dict[str, Any]
) -> DecodedURL: ...
def finalize(cls) -> None:
"Nothing to do upon finalization."
...
@attr.s(auto_attribs=True, frozen=True)
class Response:
"""
Metadata about an HTTP response, with an object that Klein knows how to
understand.
This includes:
- an HTTP response code
- some HTTP headers
- a body object, which can be anything else Klein understands; for
example, an IResource, an IRenderable, str, bytes, etc.
@since: Klein NEXT
"""
code: int = ...
headers: Mapping[
Union[str, bytes], Union[str, bytes, Sequence[Union[str, bytes]]]
] = ...
body: Any = ...

476
typings/klein/_form.pyi Normal file
View file

@ -0,0 +1,476 @@
"""
This type stub file was generated by pyright.
"""
from typing import (
Any,
AnyStr,
Callable,
Dict,
Generator,
Iterable,
List,
NoReturn,
Optional,
Sequence,
Type,
)
from zope.interface import Interface, implementer
import attr
from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.python.components import Componentized
from twisted.web.iweb import IRenderable, IRequest
from twisted.web.resource import Resource
from twisted.web.template import Element, Tag
from ._app import KleinRenderable
from ._decorators import bindable
from .interfaces import (
IDependencyInjector,
IRequestLifecycle,
IRequiredParameter,
ISession,
ValidationError,
)
class CrossSiteRequestForgery(Resource):
"""
Cross site request forgery detected. Request aborted.
"""
def __init__(self, message: str) -> None: ...
def render(self, request: IRequest) -> bytes:
"""
For all HTTP methods, return a 403.
"""
...
CSRF_PROTECTION = ...
def textConverter(value: AnyStr) -> str:
"""
Converter for form values (which may be any type of string) into text.
"""
...
class IParsedJSONBody(Interface):
"""
Marker interface for the dict parsed from the request body's JSON contents.
"""
...
@implementer(IRequiredParameter)
@attr.s(auto_attribs=True, frozen=True)
class Field:
"""
A L{Field} is a static part of a L{Form}.
@ivar converter: The converter.
"""
converter: Callable[[AnyStr], Any]
formInputType: str
pythonArgumentName: Optional[str] = ...
formFieldName: Optional[str] = ...
formLabel: Optional[str] = ...
default: Optional[Any] = ...
required: bool = ...
noLabel: bool = ...
value: str = ...
error: Optional[ValidationError] = ...
def registerInjector(
self,
injectionComponents: Componentized,
parameterName: str,
requestLifecycle: IRequestLifecycle,
) -> IDependencyInjector:
"""
Register this form field as a dependency injector.
"""
...
def maybeNamed(self, name: str) -> Field:
"""
Create a new L{Field} like this one, but with all the name default
values filled in.
@param name: the name.
"""
...
def asTags(self) -> Iterable[Tag]:
"""
Convert this L{Field} into some stuff that can be rendered in a
L{twisted.web.template}.
@return: A new set of tags to include in a template.
"""
...
def extractValue(self, request: IRequest) -> Any:
"""
Extract a value from the request.
In the case of key/value form posts, this attempts to reliably make the
value into str. In the case of a JSON post, however, it will simply
extract the value from the top-level dictionary, which means it could
be any arrangement of JSON-serializiable objects.
"""
...
def validateValue(self, value: Any) -> Any:
"""
Validate the given text and return a converted Python object to use, or
fail with L{ValidationError}.
@param value: The value that was extracted by L{Field.extractValue}.
@return: The converted value.
"""
...
@classmethod
def text(cls, **kw: Any) -> Field:
"""
Shorthand for a form field that contains a short string, and will be
rendered as a plain <input>.
"""
...
@classmethod
def password(cls, **kw: Any) -> Field:
"""
Shorthand for a form field that, like L{text}, contains a short string,
but should be obscured when typed (and, to the extent possible,
obscured in other sensitive contexts, such as logging.)
"""
...
@classmethod
def hidden(cls, name: str, value: str, **kw: Any) -> Field:
"""
Shorthand for a hidden field.
"""
...
@classmethod
def number(
cls,
minimum: Optional[int] = ...,
maximum: Optional[int] = ...,
kind: Type = ...,
**kw: Any,
) -> Field:
"""
An integer within the range [minimum, maximum].
"""
...
@classmethod
def submit(cls, value: str) -> Field:
"""
A field representing a submit button, with a value (displayed on the
button).
"""
...
@implementer(IRenderable)
@attr.s(auto_attribs=True)
class RenderableForm:
"""
An L{IRenderable} representing a renderable form.
@ivar prevalidationValues: a L{dict} mapping {L{Field}: L{list} of
L{str}}, representing the value that each field received as part of
the request.
@ivar validationErrors: a L{dict} mapping {L{Field}: L{ValidationError}}
"""
_form: IForm
_session: ISession
_action: str
_method: str
_enctype: str
_encoding: str
prevalidationValues: Dict[Field, Optional[str]] = ...
validationErrors: Dict[Field, ValidationError] = ...
ENCTYPE_FORM_DATA = ...
ENCTYPE_URL_ENCODED = ...
def lookupRenderMethod(self, name: str) -> NoReturn:
"""
Form renderers don't supply any render methods, so this just always
raises L{MissingRenderMethod}.
"""
...
def render(self, request: IRequest) -> Tag:
"""
Render this form to the given request.
"""
...
def glue(self) -> List[Tag]:
"""
Provide any glue necessary to render this form; this must be dropped
into the template within the C{<form>} tag.
Presently, this glue includes only the CSRF token argument, but Klein
reserves the right to add arbitrary HTML here. This should not create
any user-visible content, however.
@return: some HTML elements in the form of renderable objects for
L{twisted.web.template}
"""
...
@bindable
def defaultValidationFailureHandler(
instance: Optional[object], request: IRequest, fieldValues: FieldValues
) -> Element:
"""
This is the default validation failure handler, which will be used by form
handlers (i.e. any routes which use L{klein.Requirer} to require a field)
in the case of any input validation failure when no other validation
failure handler is registered via L{Form.onValidationFailureFor}.
Its behavior is to simply return an HTML rendering of the form object,
which includes inline information about fields which failed to validate.
@param instance: The instance associated with the router that the form
handler was handled on.
@param request: The request including the form submission.
@return: Any object acceptable from a Klein route.
"""
...
_requirerFunctionWithForm = Any
_routeCallable = Any
class IProtoForm(Interface):
"""
Marker interface for L{ProtoForm}.
"""
fields: Sequence[Field] = ...
def addField(field: Field) -> FieldInjector:
"""
Add the given field to the form ultimately created here.
"""
...
class IForm(Interface):
"""
Marker interface for form attached to dependency injection components.
"""
fields: Sequence[Field] = ...
def populateRequestValues(
injectionComponents: Componentized, instance: Any, request: IRequest
) -> Deferred:
"""
Extract the values present in this request and populate a
L{FieldValues} object.
"""
...
@implementer(IProtoForm)
@attr.s(auto_attribs=True)
class ProtoForm:
"""
Form-builder.
"""
_componentized: Componentized
_lifecycle: IRequestLifecycle
fields: List[Field] = ...
@classmethod
def fromComponentized(cls, componentized: Componentized) -> ProtoForm:
"""
Create a ProtoForm from a componentized object.
"""
...
def addField(self, field: Field) -> FieldInjector: ...
class IFieldValues(Interface):
"""
Marker interface for parsed fields.
"""
form: IForm = ...
arguments: Dict[str, Any] = ...
prevalidationValues: Dict[Field, Optional[str]] = ...
validationErrors: Dict[Field, ValidationError] = ...
def validate(instance: Any, request: IRequest) -> Deferred:
"""
If any validation errors have occurred, raise a relevant exception.
"""
...
@implementer(IFieldValues)
@attr.s(auto_attribs=True)
class FieldValues:
"""
Reified post-parsing values for HTTP form submission.
"""
form: Form
arguments: Dict[str, Any]
prevalidationValues: Dict[Field, Optional[str]]
validationErrors: Dict[Field, ValidationError]
_injectionComponents: Componentized
@inlineCallbacks
def validate(
self, instance: Any, request: IRequest
) -> Generator[Any, object, None]: ...
@implementer(IDependencyInjector)
@attr.s(auto_attribs=True)
class FieldInjector:
"""
Field injector.
"""
_componentized: Componentized
_field: Field
_lifecycle: IRequestLifecycle
def injectValue(
self, instance: Any, request: IRequest, routeParams: Dict[str, Any]
) -> Any:
"""
Inject the given value into the form.
"""
...
def finalize(self) -> None:
"""
Finalize this ProtoForm into a real form.
"""
...
class IValidationFailureHandler(Interface):
"""
Validation failure handler callable interface.
"""
def __call__(request: IRequest) -> KleinRenderable:
"""
Called to handle a validation failure.
"""
...
def checkCSRF(request: IRequest) -> None:
"""
Check the request for cross-site request forgery, raising an EarlyExit if
it is found.
"""
...
@implementer(IForm)
@attr.s(auto_attribs=True, hash=False)
class Form:
"""
A L{Form} is a collection of fields attached to a function.
"""
fields: Sequence[Field]
@staticmethod
def onValidationFailureFor(
handler: _requirerFunctionWithForm,
) -> Callable[[Callable], Callable]:
"""
Register a function to be run in the event of a validation failure for
the input to a particular form handler.
Generally used like so::
requirer = Requirer(...)
@requirer.prerequisite([ISession])
def procureSession(request):
return SessionProcurer(...).procureSession(request)
router = Klein()
@requirer.require(router.route("/", methods=['POST']),
someField=Field.text())
def formHandler(someField):
...
# Handle input validation failures for handleForm
@Form.onValidationFailureFor(formHandler)
def handleValidationFailures(request, fieldValues):
return "Your inputs didn't validate."
@see: L{defaultValidationFailureHandler} for a more detailed
description of the decorated function's expected signature. The
additional parameter it is passed is a L{FieldValues} instance.
@param handler: The form handler - i.e. function decorated by
L{Form.handler} - for which the decorated function will handle
validation failures.
@return: a decorator that decorates a function with the signature
C{(request, form) -> thing klein can render}.
"""
...
@inlineCallbacks
def populateRequestValues(
self, injectionComponents: Componentized, instance: Any, request: IRequest
) -> Generator[Any, object, None]: ...
@classmethod
def rendererFor(
cls,
decoratedFunction: _requirerFunctionWithForm,
action: str,
method: str = ...,
enctype: str = ...,
encoding: str = ...,
) -> RenderableFormParam:
"""
A form parameter that can render a form declared as a number of fields
on another route.
Use like so::
class MyFormApp:
router = Klein()
requirer = Requirer()
@requirer.require(
router.route("/handle-form", methods=["POST"]),
name=Field.text(), value=Field.integer(),
)
def formRoute(self, name, value):
...
@requirer.require(
router.route("/show-form", methods=["GET"]),
form=Form.rendererFor(formRoute)
)
def showForm(self, form):
return form
As a L{RenderableForm} provides L{IRenderable}, you may return the
parameter directly
"""
...
@implementer(IRequiredParameter, IDependencyInjector)
@attr.s(auto_attribs=True)
class RenderableFormParam:
"""
A L{RenderableFormParam} implements L{IRequiredParameter} and
L{IDependencyInjector} to provide a L{RenderableForm} to your route.
"""
_form: IForm
_action: str
_method: str
_enctype: str
_encoding: str
def registerInjector(
self,
injectionComponents: Componentized,
parameterName: str,
requestLifecycle: IRequestLifecycle,
) -> RenderableFormParam: ...
def injectValue(
self, instance: Any, request: IRequest, routeParams: Dict[str, Any]
) -> RenderableForm:
"""
Create the renderable form from the request.
"""
...
def finalize(self) -> None:
"""
Nothing to do upon finalization.
"""
...

View file

@ -0,0 +1,94 @@
"""
This type stub file was generated by pyright.
"""
from typing import AnyStr, Iterable, Tuple, Union
from zope.interface import implementer
from attr import attrs
from ._imessage import (
IHTTPHeaders,
IMutableHTTPHeaders,
MutableRawHeaders,
RawHeader,
RawHeaders,
)
"""
HTTP headers API.
"""
__all__ = ()
String = Union[bytes, str]
HEADER_NAME_ENCODING = ...
HEADER_VALUE_ENCODING = ...
def headerNameAsBytes(name: String) -> bytes:
"""
Convert a header name to bytes if necessary.
"""
...
def headerNameAsText(name: String) -> str:
"""
Convert a header name to str if necessary.
"""
...
def headerValueAsBytes(value: String) -> bytes:
"""
Convert a header value to bytes if necessary.
"""
...
def headerValueAsText(value: String) -> str:
"""
Convert a header value to str if necessary.
"""
...
def normalizeHeaderName(name: AnyStr) -> AnyStr:
"""
Normalize a header name.
"""
...
def normalizeRawHeaders(
headerPairs: Iterable[Iterable[String]],
) -> Iterable[RawHeader]: ...
def normalizeRawHeadersFrozen(headerPairs: Iterable[Iterable[bytes]]) -> RawHeaders: ...
def normalizeRawHeadersMutable(
headerPairs: Iterable[Iterable[bytes]],
) -> MutableRawHeaders: ...
def getFromRawHeaders(rawHeaders: RawHeaders, name: AnyStr) -> Iterable[AnyStr]:
"""
Get a value from raw headers.
"""
...
def rawHeaderName(name: String) -> bytes: ...
def rawHeaderNameAndValue(name: String, value: String) -> Tuple[bytes, bytes]: ...
@implementer(IHTTPHeaders)
@attrs(frozen=True)
class FrozenHTTPHeaders:
"""
Immutable HTTP entity headers.
"""
rawHeaders: RawHeaders = ...
def getValues(self, name: AnyStr) -> Iterable[AnyStr]: ...
@implementer(IMutableHTTPHeaders)
@attrs(frozen=True)
class MutableHTTPHeaders:
"""
Mutable HTTP entity headers.
"""
_rawHeaders: MutableRawHeaders = ...
@property
def rawHeaders(self) -> RawHeaders: ...
def getValues(self, name: AnyStr) -> Iterable[AnyStr]: ...
def remove(self, name: String) -> None: ...
def addValue(self, name: AnyStr, value: AnyStr) -> None: ...

View file

@ -0,0 +1,34 @@
"""
This type stub file was generated by pyright.
"""
from typing import AnyStr, Iterable
from zope.interface import implementer
from attr import attrs
from twisted.web.http_headers import Headers
from ._headers import IMutableHTTPHeaders, RawHeaders, String
"""
Support for interoperability with L{twisted.web.http_headers.Headers}.
"""
__all__ = ()
@implementer(IMutableHTTPHeaders)
@attrs(frozen=True)
class HTTPHeadersWrappingHeaders:
"""
HTTP entity headers.
This is an L{IMutableHTTPHeaders} implementation that wraps a L{Headers}
object.
"""
_headers: Headers = ...
@property
def rawHeaders(self) -> RawHeaders: ...
def getValues(self, name: AnyStr) -> Iterable[AnyStr]: ...
def remove(self, name: String) -> None: ...
def addValue(self, name: AnyStr, value: AnyStr) -> None: ...

21
typings/klein/_iform.pyi Normal file
View file

@ -0,0 +1,21 @@
"""
This type stub file was generated by pyright.
"""
class ValidationError(Exception):
"""
A L{ValidationError} is raised by L{Field.extractValue}.
"""
def __init__(self, message: object) -> None:
"""
Initialize a L{ValidationError} with a message to show to the user.
"""
...
class ValueAbsent(ValidationError):
"""
A value was required but none was supplied.
"""
...

164
typings/klein/_imessage.pyi Normal file
View file

@ -0,0 +1,164 @@
"""
This type stub file was generated by pyright.
"""
from typing import AnyStr, Iterable, MutableSequence, Sequence, Tuple
from zope.interface import Interface
from hyperlink import DecodedURL
from tubes.itube import IFount
"""
Interfaces related to HTTP messages.
Do not import directly from here, except:
- From _interfaces.py.
- From implementations of these interfaces, but even then, import the
zope.interface.Interface classes via _interfaces.py.
This will ensure that type checking works.
"""
__all__ = ()
RawHeader = Tuple[bytes, bytes]
RawHeaders = Sequence[RawHeader]
MutableRawHeaders = MutableSequence[RawHeader]
class FountAlreadyAccessedError(Exception):
"""
The HTTP message's fount has already been accessed and is no longer
available.
"""
...
class IHTTPHeaders(Interface):
"""
HTTP entity headers.
HTTP headers names and values are sort-of-but-not-quite-specifically
expected to be text.
Because the specifications are somewhat vague, and implementations vary in
fidelity, both header names and values must be made available as the
original bytes received from the network.
This interface also makes them available as an ordered sequence of name and
value pairs so that they can be iterated in the same order as they were
received on the network.
As such, the C{rawHeaders} attribute provides the header data as a sequence
of C{(name, value)} L{tuple}s.
A dictionary-like interface that maps text names to an ordered sequence of
text values.
This interface assumes that both header name bytes and header value bytes
are encoded as ISO-8859-1.
Note that header name bytes should be strictly encoded as ASCII; this
interface uses ISO-8859-1 to provide interoperability with (naughty) HTTP
implementations that send non-ASCII data.
Because ISO-8859-1 is a superset of ASCII, this will still work for
well-behaved implementations.
"""
rawHeaders: RawHeaders = ...
def getValues(name: AnyStr) -> Iterable[AnyStr]:
"""
Get the values associated with the given header name.
If the given name is L{bytes}, the value will be returned as the raw
header L{bytes}.
If the given name is L{str}, the name will be encoded as ISO-8859-1
and the value will be returned as text, by decoding the raw header
value bytes with ISO-8859-1.
@param name: The name of the header to look for.
@return: The values of the header with the given name.
"""
...
class IMutableHTTPHeaders(IHTTPHeaders):
"""
Mutable HTTP entity headers.
"""
def remove(name: AnyStr) -> None:
"""
Remove all header name/value pairs for the given header name.
If the given name is L{str}, it will be encoded as ISO-8859-1 before
comparing to the (L{bytes}) header names.
@param name: The name of the header to remove.
"""
...
def addValue(name: AnyStr, value: AnyStr) -> None:
"""
Add the given header name/value pair.
If the given name is L{bytes}, the value must also be L{bytes}.
If the given name is L{str}, it will be encoded as ISO-8859-1, and the
value, which must also be L{str}, will be encoded as ISO-8859-1.
"""
...
class IHTTPMessage(Interface):
"""
HTTP entity.
"""
headers: IHTTPHeaders = ...
def bodyAsFount() -> IFount:
"""
The entity body, as a fount.
@note: The fount may only be accessed once.
It provides a mechanism for accessing the body as a stream of data,
potentially as it is read from the network, without having to cache
the entire body, which may be large.
Because there is no caching, it is not possible to "start over" by
accessing the fount a second time.
Attempting to do so will raise L{FountAlreadyAccessedError}.
@raise FountAlreadyAccessedError: If the fount has previously been
accessed.
"""
...
async def bodyAsBytes() -> bytes:
"""
The entity body, as bytes.
@note: This necessarily reads the entire entity body into memory,
which may be a problem if the body is large.
@note: This method caches the body, which means that unlike
C{self.bodyAsFount}, calling it repeatedly will return the same
data.
@note: This method accesses the fount (via C{self.bodyAsFount}), which
means the fount will not be available afterwards, and that if
C{self.bodyAsFount} has previously been called directly, this
method will raise L{FountAlreadyAccessedError}.
@raise FountAlreadyAccessedError: If the fount has previously been
accessed.
"""
...
class IHTTPRequest(IHTTPMessage):
"""
HTTP request.
"""
method: str = ...
uri: DecodedURL = ...
class IHTTPResponse(IHTTPMessage):
"""
HTTP response.
"""
status: int = ...

View file

@ -0,0 +1,32 @@
"""
This type stub file was generated by pyright.
"""
from typing import Mapping, Optional, Union
from zope.interface import Interface
"""
Internal interface definitions.
All Zope Interface classes should be imported from here so that type checking
works, since mypy doesn't otherwise get along with Zope Interface.
"""
KleinQueryValue = Union[str, int, float]
class IKleinRequest(Interface):
branch_segments = ...
mapper = ...
def url_for(
endpoint: str,
values: Optional[Mapping[str, KleinQueryValue]] = ...,
method: Optional[str] = ...,
force_external: bool = ...,
append_unknown: bool = ...,
) -> str:
"""
L{werkzeug.routing.MapAdapter.build}
"""
...
__all__ = ()

326
typings/klein/_isession.pyi Normal file
View file

@ -0,0 +1,326 @@
"""
This type stub file was generated by pyright.
"""
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Sequence, Type
from zope.interface import Interface
import attr
from constantly import Names
from twisted.internet.defer import Deferred
from twisted.python.components import Componentized
from twisted.web.iweb import IRequest
from ._app import KleinRenderable
if TYPE_CHECKING: ...
class NoSuchSession(Exception):
"""
No such session could be found.
"""
...
class TooLateForCookies(Exception):
"""
It's too late to set a cookie.
"""
...
class TransactionEnded(Exception):
"""
Exception raised when.
"""
...
class SessionMechanism(Names):
"""
Mechanisms which can be used to identify and authenticate a session.
@cvar Cookie: The Cookie session mechanism involves looking up the session
identifier via an HTTP cookie. Session objects retrieved via this
mechanism may be vulnerable to U{CSRF attacks
<https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)>}
and therefore must have CSRF protections applied to them.
@cvar Header: The Header mechanism retrieves the session identifier via a
separate header such as C{"X-Auth-Token"}. Since a different-origin
site in a browser can easily send a form submission including cookies,
but I{can't} easily put stuff into other arbitrary headers, this does
not require additional protections.
"""
Cookie = ...
Header = ...
class ISession(Interface):
"""
An L{ISession} provider contains an identifier for the session, information
about how the session was negotiated with the client software, and
"""
identifier = ...
isConfidential = ...
authenticatedBy = ...
def authorize(interfaces: Iterable[Type[Interface]]) -> Deferred:
"""
Retrieve other objects from this session.
This method is how you can retrieve application-specific objects from
the general-purpose session; define interfaces for each facet of
something accessible to a session, then pass it here and to the
L{ISessionStore} implementation you're using.
@param interfaces: A list of interfaces.
@return: all of the providers that could be retrieved from the session.
@rtype: L{Deferred} firing with L{dict} mapping
L{zope.interface.interfaces.IInterface} to providers of each
interface. Interfaces which cannot be authorized will not be
present as keys in this dictionary.
"""
...
class ISessionStore(Interface):
"""
Backing storage for sessions.
"""
def newSession(isConfidential: bool, authenticatedBy: SessionMechanism) -> Deferred:
"""
Create a new L{ISession}.
@return: a new session with a new identifier.
@rtype: L{Deferred} firing with L{ISession}.
"""
...
def loadSession(
identifier: str, isConfidential: bool, authenticatedBy: SessionMechanism
) -> Deferred:
"""
Load a session given the given identifier and security properties.
As an optimization for session stores where the back-end can generate
session identifiers when the presented one is not found in the same
round-trip to a data store, this method may return a L{Session} object
with an C{identifier} attribute that does not match C{identifier}.
However, please keep in mind when implementing L{ISessionStore} that
this behavior is only necessary for requests where C{authenticatedBy}
is L{SessionMechanism.Cookie}; an unauthenticated
L{SessionMechanism.Header} session is from an API client and its
session should be valid already.
@return: an existing session with the given identifier.
@rtype: L{Deferred} firing with L{ISession} or failing with
L{NoSuchSession}.
"""
...
def sentInsecurely(identifiers: Sequence[str]) -> None:
"""
The transport layer has detected that the given identifiers have been
sent over an unauthenticated transport.
"""
...
class ISimpleAccountBinding(Interface):
"""
Data-store agnostic account / session binding manipulation API for "simple"
accounts - i.e. those using username, password, and email address as a
method to authenticate a user.
This goes into a user-authentication-capable L{ISession} object's C{data}
attribute as a component.
"""
def bindIfCredentialsMatch(username: str, password: str) -> None:
"""
Attach the session this is a component of to an account with the given
username and password, if the given username and password correctly
authenticate a principal.
"""
...
def boundAccounts() -> Deferred:
"""
Retrieve the accounts currently associated with the session this is a
component of.
@return: L{Deferred} firing with a L{list} of L{ISimpleAccount}.
"""
...
def unbindThisSession() -> None:
"""
Disassociate the session this is a component of from any accounts it's
logged in to.
"""
...
def createAccount(username: str, email: str, password: str) -> None:
"""
Create a new account with the given username, email and password.
"""
...
class ISimpleAccount(Interface):
"""
Data-store agnostic account interface.
"""
username = ...
accountID = ...
def bindSession(session: ISession) -> None:
"""
Bind the given session to this account; i.e. authorize the given
session to act on behalf of this account.
"""
...
def changePassword(newPassword: str) -> None:
"""
Change the password of this account.
"""
...
class ISessionProcurer(Interface):
"""
An L{ISessionProcurer} wraps an L{ISessionStore} and can procure sessions
that store, given HTTP request objects.
"""
def procureSession(request: IRequest, forceInsecure: bool = ...) -> Deferred:
"""
Retrieve a session using whatever technique is necessary.
If the request already identifies an existing session in the store,
retrieve it. If not, create a new session and retrieve that.
@param request: The request to procure a session from.
@param forceInsecure: Even if the request was transmitted securely
(i.e. over HTTPS), retrieve the session that would be used by the
same browser if it were sending an insecure (i.e. over HTTP)
request; by default, this is False, and the session's security will
match that of the request.
@return: a L{Deferred} that:
- fires with an L{ISession} provider if the request describes
an existing, valid session, or, if the intersection of the
data in the request and the configuration of this
L{ISessionProcurer} allow for a cookie to be set immediately,
or
- fails with L{NoSuchSession} if the request is unable to
negotiate a session based on the current request: this is
generally if the client is trying to use header-based
authentication (and therefore does not want a new cookie set)
or if this procurer is configured not to automatically create
new sessions on the fly, or
- fails with L{TooLateForCookies} if the request bound to this
procurer has already sent the headers and therefore we can no
longer set a cookie, and we need to set a cookie.
"""
...
class IDependencyInjector(Interface):
"""
An injector for a given dependency.
"""
def injectValue(
instance: Any, request: IRequest, routeParams: Dict[str, Any]
) -> Any:
"""
Return a value to be injected into the parameter name specified by the
IRequiredParameter. This may return a Deferred, or an object, or an
object directly providing the relevant interface.
@param instance: The instance to which the Klein router processing this
request is bound.
@param request: The request being processed.
@param routeParams: A (read-only) copy of the the arguments passed to
the route by the layer below dependency injection (for example, URL
parameters).
"""
...
def finalize() -> None:
"""
Finalize this injector before allowing the route to be created.
Finalization generally includes:
- attaching any hooks to the request lifecycle object that need to
be run before/after each request
- attaching any finalized component objects to the
injectionComponents originally passed along to the
IRequiredParameter that created this IDependencyInjector.
"""
...
class IRequestLifecycle(Interface):
"""
Interface for adding hooks to the phases of a request's lifecycle.
"""
def addPrepareHook(
beforeHook: Callable,
requires: Sequence[Type[Interface]] = ...,
provides: Sequence[Type[Interface]] = ...,
) -> None:
"""
Add a hook that promises to prepare the request by supplying the given
interfaces as components on the request, and requires the given
requirements.
Prepare hooks are run I{before any} L{IDependencyInjector}s I{inject
their values}.
"""
...
class IRequiredParameter(Interface):
"""
A declaration that a given Python parameter is required to satisfy a given
dependency at request-handling time.
"""
def registerInjector(
injectionComponents: Componentized,
parameterName: str,
lifecycle: IRequestLifecycle,
) -> IDependencyInjector:
"""
Register the given injector at method-decoration time, informing it of
its Python parameter name.
@note: this happens at I{route definition} time, after all other
injectors have been registered by
L{IRequiredParameter.registerInjector}.
@param lifecycle: An L{IRequestLifecycle} provider which contains hooks
that will be run before and after each request. If this injector
has shared per-request dependencies that need to be executed before
or after the request is processed, this method should attach them
to those lists. These hooks are supplied here rather than relying
on C{injectValue} to run the requisite logic each time so that
DependencyInjectors may cooperate on logic that needs to be
duplicated, such as provisioning a session.
"""
...
@attr.s(auto_attribs=True)
class EarlyExit(Exception):
"""
An L{EarlyExit} may be raised by any of the code that runs in the
before-request dependency injection code path when using
L{klein.Requirer.require}.
@ivar alternateReturnValue: The return value which should instead be
supplied as the route's response.
"""
alternateReturnValue: KleinRenderable
...

View file

@ -0,0 +1,41 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, Optional, Union
from attr import attrs
from tubes.itube import IFount
"""
HTTP message API.
"""
__all__ = ()
InternalBody = Union[bytes, IFount]
@attrs(frozen=False)
class MessageState:
"""
Internal mutable state for HTTP message implementations in L{klein}.
"""
cachedBody: Optional[bytes] = ...
fountExhausted: bool = ...
def validateBody(instance: Any, attribute: Any, body: InternalBody) -> None:
"""
Validator for L{InternalBody}.
"""
...
def bodyAsFount(body: InternalBody, state: MessageState) -> IFount:
"""
Return a fount for a given L{InternalBody}.
"""
...
async def bodyAsBytes(body: InternalBody, state: MessageState) -> bytes:
"""
Return bytes for a given L{InternalBody}.
"""
...

109
typings/klein/_plating.pyi Normal file
View file

@ -0,0 +1,109 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, Callable, Generator, List, Tuple
import attr
from twisted.internet.defer import inlineCallbacks
from twisted.web.template import Element
"""
Templating wrapper support for Klein.
"""
StackType = List[Tuple[Any, Callable[[Any], None]]]
ATOM_TYPES = ...
@inlineCallbacks
def resolveDeferredObjects(root: Any) -> Generator[Any, object, Any]:
"""
Wait on possibly nested L{Deferred}s that represent a JSON
serializable object.
@param root: JSON-serializable object that may contain
L{Deferred}s that resolve to JSON-serializable objects, or a
L{Deferred} that resolves to one.
@return: A L{Deferred} that fires with a L{Deferred}-free version
of C{root}, or that fails with the first exception
encountered.
"""
...
class PlatedElement(Element):
"""
The element type returned by L{Plating}. This contains several utility
renderers.
"""
def __init__(
self, slot_data, preloaded, boundInstance, presentationSlots, renderers
) -> None:
"""
@param slot_data: A dictionary mapping names to values.
@param preloaded: The pre-loaded data.
"""
...
def lookupRenderMethod(
self, name
): # -> ((request: IRequest, tag: Tag, *args: Any, **kw: Any) -> Any) | ((request: Unknown, tag: Unknown) -> Generator[Unknown, None, None]):
"""
@return: a renderer.
"""
...
class Plating:
"""
A L{Plating} is a container which can be used to generate HTML from data.
Its name is derived both from tem-I{plating} and I{chrome plating}.
"""
CONTENT = ...
def __init__(self, defaults=..., tags=..., presentation_slots=...) -> None:
""" """
...
def renderMethod(self, renderer):
"""
Add a render method to this L{Plating} object that can be used in the
top-level template.
The name of the renderer to use within the template is the name of the
decorated function.
"""
...
def routed(self, routing, tags): # -> (method: Unknown) -> Unknown:
""" """
...
@attr.s(auto_attribs=True)
class _Widget:
"""
Implementation of L{Plating.widgeted}. This is a L{callable}
descriptor that records the instance to which its wrapped
function is bound, if any. Its L{widget} method then passes
that instance or L{None} and the result of invoking the
function (or now bound method) to the creating L{Plating}
instance's L{Plating._elementify} to construct a
L{PlatedElement}.
"""
_plating: Plating
_function: Callable[..., Any]
_instance: object
def __call__(self, *args, **kwargs): ...
def __get__(self, instance, owner=...): ...
def widget(self, *args, **kwargs): # -> PlatedElement:
"""
Construct a L{PlatedElement} the rendering of this widget.
"""
...
def __getattr__(self, attr): ...
def widgeted(self, function): # -> _Widget:
"""
A decorator that turns a function into a renderer for an
element without a L{Klein.route}. Use this to create reusable
template elements.
"""
...

View file

@ -0,0 +1,34 @@
"""
This type stub file was generated by pyright.
"""
from typing import Union
from zope.interface import implementer
from attr import attrs
from hyperlink import DecodedURL
from tubes.itube import IFount
from ._imessage import IHTTPHeaders, IHTTPRequest
from ._message import MessageState
"""
HTTP request API.
"""
__all__ = ()
@implementer(IHTTPRequest)
@attrs(frozen=True)
class FrozenHTTPRequest:
"""
Immutable HTTP request.
"""
method: str = ...
uri: DecodedURL = ...
headers: IHTTPHeaders = ...
_body: Union[bytes, IFount] = ...
_state: MessageState = ...
def bodyAsFount(self) -> IFount: ...
async def bodyAsBytes(self) -> bytes: ...

View file

@ -0,0 +1,40 @@
"""
This type stub file was generated by pyright.
"""
from zope.interface import implementer
from attr import attrs
from hyperlink import DecodedURL
from tubes.itube import IFount
from twisted.web.iweb import IRequest
from ._headers import IHTTPHeaders
from ._message import MessageState
from ._request import IHTTPRequest
"""
Support for interoperability with L{twisted.web.iweb.IRequest}.
"""
__all__ = ()
noneIO = ...
@implementer(IHTTPRequest)
@attrs(frozen=True)
class HTTPRequestWrappingIRequest:
"""
HTTP request.
This is an L{IHTTPRequest} implementation that wraps an L{IRequest} object.
"""
_request: IRequest = ...
_state: MessageState = ...
@property
def method(self) -> str: ...
@property
def uri(self) -> DecodedURL: ...
@property
def headers(self) -> IHTTPHeaders: ...
def bodyAsFount(self) -> IFount: ...
async def bodyAsBytes(self) -> bytes: ...

View file

@ -0,0 +1,82 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, Callable, Generator, List, Sequence, Type
from zope.interface import Interface, implementer
import attr
from twisted.internet.defer import inlineCallbacks
from twisted.web.iweb import IRequest
from .interfaces import IRequestLifecycle, IRequiredParameter
@implementer(IRequestLifecycle)
@attr.s(auto_attribs=True)
class RequestLifecycle:
"""
Mechanism to run hooks at the start of a request managed by a L{Requirer}.
"""
_prepareHooks: List = ...
def addPrepareHook(
self,
beforeHook: Callable,
requires: Sequence[Type[Interface]] = ...,
provides: Sequence[Type[Interface]] = ...,
) -> None: ...
@inlineCallbacks
def runPrepareHooks(
self, instance: Any, request: IRequest
) -> Generator[Any, object, None]:
"""
Execute all the hooks added with L{RequestLifecycle.addPrepareHook}.
This is invoked by the L{requires} route machinery.
@param instance: The instance bound to the Klein route.
@param request: The IRequest being processed.
"""
...
_routeDecorator = Any
_routeT = Any
_prerequisiteCallback = Callable[[IRequestLifecycle], None]
@attr.s(auto_attribs=True)
class Requirer:
"""
Dependency injection for required parameters.
"""
_prerequisites: List[_prerequisiteCallback] = ...
def prerequisite(
self,
providesComponents: Sequence[Type[Interface]],
requiresComponents: Sequence[Type[Interface]] = ...,
) -> Callable[[Callable], Callable]:
"""
Specify a component that is a pre-requisite of every request routed
through this requirer's C{require} method. Used like so::
requirer = Requirer()
@requirer.prerequisite([IFoo])
@inlineCallbacks
def fooForRequest(request):
request.setComponent(IFoo, someFooComponent)
@note: C{requiresComponents} is, at this point, for the reader's
interest only, the framework will not topologically sort
dependencies; you must presently register prerequisites in the
order you want them to be called.
"""
...
def require(
self, routeDecorator: _routeT, **requiredParameters: IRequiredParameter
) -> _routeDecorator:
"""
Inject the given dependencies while running the given route.
"""
...

View file

@ -0,0 +1,72 @@
"""
This type stub file was generated by pyright.
"""
from typing import TYPE_CHECKING, Sequence, Tuple, Union
from twisted.python.failure import Failure
from twisted.web.iweb import IRequest
from twisted.web.resource import Resource
from ._app import Klein, KleinRenderable
if TYPE_CHECKING: ...
def ensure_utf8_bytes(v: Union[str, bytes]) -> bytes:
"""
Coerces a value which is either a C{str} or C{bytes} to a C{bytes}.
If ``v`` is a C{str} object it is encoded as utf-8.
"""
...
class _StandInResource:
"""
A standin for a Resource.
This is a sentinel value for L{KleinResource}, to say that we are rendering
a L{Resource}, which may close the connection itself later.
"""
...
StandInResource = ...
class URLDecodeError(Exception):
"""
Raised if one or more string parts of the URL could not be decoded.
"""
__slots__ = ...
def __init__(self, errors: Sequence[Tuple[str, Failure]]) -> None:
"""
@param errors: Sequence of decoding errors, expressed as tuples
of names and an associated failure.
"""
...
def __repr__(self) -> str: ...
def extractURLparts(request: IRequest) -> Tuple[str, str, int, str, str]:
"""
Extracts and decodes URI parts from C{request}.
All strings must be UTF8-decodable.
@param request: A Twisted Web request.
@raise URLDecodeError: If one of the parts could not be decoded as UTF-8.
@return: L{tuple} of the URL scheme, the server name, the server port, the
path info and the script name.
"""
...
class KleinResource(Resource):
"""
A ``Resource`` that can do URL routing.
"""
isLeaf = ...
def __init__(self, app: Klein) -> None: ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, other: object) -> bool: ...
def render(self, request: IRequest) -> KleinRenderable: ...

View file

@ -0,0 +1,32 @@
"""
This type stub file was generated by pyright.
"""
from typing import Union
from zope.interface import implementer
from attr import attrs
from tubes.itube import IFount
from ._imessage import IHTTPHeaders, IHTTPResponse
from ._message import MessageState
"""
HTTP response API.
"""
__all__ = ()
@implementer(IHTTPResponse)
@attrs(frozen=True)
class FrozenHTTPResponse:
"""
Immutable HTTP response.
"""
status: int = ...
headers: IHTTPHeaders = ...
_body: Union[bytes, IFount] = ...
_state: MessageState = ...
def bodyAsFount(self) -> IFount: ...
async def bodyAsBytes(self) -> bytes: ...

145
typings/klein/_session.pyi Normal file
View file

@ -0,0 +1,145 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, Callable, Dict, Optional, Type
from zope.interface import Interface, implementer
import attr
from twisted.internet.defer import inlineCallbacks
from twisted.python.components import Componentized
from twisted.web.iweb import IRequest
from twisted.web.resource import Resource
from .interfaces import (
IDependencyInjector,
IRequestLifecycle,
IRequiredParameter,
ISessionProcurer,
ISessionStore,
)
@implementer(ISessionProcurer)
@attr.s(auto_attribs=True)
class SessionProcurer:
"""
A L{SessionProcurer} procures a session from a request and a store.
@ivar _store: The session store to procure a session from.
@ivar _maxAge: The maximum age (in seconds) of the session cookie.
@ivar _secureCookie: The name of the cookie to use for sessions protected
with TLS (i.e. HTTPS).
@ivar _insecureCookie: The name of the cookie to use for sessions I{not}
protected with TLS (i.e. HTTP).
@ivar _cookieDomain: If set, the domain name to restrict the session cookie
to.
@ivar _cookiePath: If set, the URL path to restrict the session cookie to.
@ivar _secureTokenHeader: The name of the HTTPS header to try to extract a
session token from; API clients should use this header, rather than a
cookie.
@ivar _insecureTokenHeader: The name of the HTTP header to try to extract a
session token from; API clients should use this header, rather than a
cookie.
@ivar _setCookieOnGET: Automatically request that the session store create
a session if one is not already associated with the request and the
request is a GET.
"""
_store: ISessionStore
_maxAge: int = ...
_secureCookie: bytes = ...
_insecureCookie: bytes = ...
_cookieDomain: Optional[bytes] = ...
_cookiePath: bytes = ...
_secureTokenHeader: bytes = ...
_insecureTokenHeader: bytes = ...
_setCookieOnGET: bool = ...
@inlineCallbacks
def procureSession(self, request: IRequest, forceInsecure: bool = ...) -> Any: ...
class AuthorizationDenied(Resource):
def __init__(self, interface: Type[Interface], instance: Any) -> None: ...
def render(self, request: IRequest) -> bytes: ...
@implementer(IDependencyInjector, IRequiredParameter)
@attr.s(auto_attribs=True)
class Authorization:
"""
Declare that a C{require}-decorated function requires a certain interface
be authorized from the session.
This is a dependency injector used in conjunction with a L{klein.Requirer},
like so::
from klein import Requirer, SesssionProcurer
from klein.interfaces import ISession
from myapp import ISuperDuperAdmin
requirer = Requirer()
procurer = SessionProcurer(store=someSessionStore)
@requirer.prerequisite(ISession)
def sessionize(request):
return procurer.procureSession(request)
app = Klein()
@requirer.require(
app.route("/admin"),
adminPowers=Authorization(ISuperDuperAdmin)
)
def myRoute(adminPowers):
return 'ok admin: ' + adminPowers.doAdminThing()
In this example, ISuperDuperAdmin is an interface known to your
application, and (via authorization plugins depending on your session
storage backend) to your session store. It has a doAdminThing method.
When a user hits /admin in their browser, if they are duly authorized,
they'll see 'ok admin: ' and whatever the super-secret result of
doAdminThing is. If not, by default, they'll simply get an HTTP
UNAUTHORIZED response that says "myapp.ISuperDuperAdmin DENIED". (This
behavior can be customized via the C{whenDenied} parameter to
L{Authorization}.)
@ivar _interface: the interface that is required. a provider of this
interface is what will be dependency-injected.
@ivar _required: is this authorization required? If so (the default),
don't invoke the application code if it cannot be authorized by the
procured session, and instead return the object specified by whenDenied
from the dependency-injection process. If not, then just pass None if
it is not on the session.
@ivar _whenDenied: when this authorization is denied, what object - usually
an IResource - should be returned to the route decorator that was
passed to L{Requirer.require}? Note that this will never be used if
C{required} is set to C{False}.
"""
_interface: Type[Interface]
_required: bool = ...
_whenDenied: Callable[[Type[Interface], Any], Any] = ...
def registerInjector(
self,
injectionComponents: Componentized,
parameterName: str,
lifecycle: IRequestLifecycle,
) -> IDependencyInjector:
"""
Register this authorization to inject a parameter.
"""
...
@inlineCallbacks
def injectValue(
self, instance: Any, request: IRequest, routeParams: Dict[str, Any]
) -> Any:
"""
Inject a value by asking the request's session.
"""
...
def finalize(self) -> None:
"""
Nothing to finalize when registering.
"""
...

33
typings/klein/_tubes.pyi Normal file
View file

@ -0,0 +1,33 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, BinaryIO
from zope.interface import implementer
from attr import attrs
from tubes.itube import IDrain, IFount, ISegment
"""
Extensions to Tubes.
"""
__all__ = ()
async def fountToBytes(fount: IFount) -> bytes: ...
def bytesToFount(data: bytes) -> IFount: ...
@implementer(IFount)
@attrs(frozen=False)
class IOFount:
"""
Fount that reads from a file-like-object.
"""
outputType = ISegment
_source: BinaryIO = ...
drain: IDrain = ...
_paused = ...
def __attrs_post_init__(self) -> None: ...
def flowTo(self, drain: IDrain) -> IFount: ...
def pauseFlow(self) -> Any: ...
def stopFlow(self) -> Any: ...

View file

@ -0,0 +1,9 @@
"""
This type stub file was generated by pyright.
"""
"""
Provides klein version information.
"""
__version__ = ...
__all__ = ["__version__"]

15
typings/klein/app.pyi Normal file
View file

@ -0,0 +1,15 @@
"""
This type stub file was generated by pyright.
"""
__all__ = (
"Klein",
"KleinRequest",
"handle_errors",
"resource",
"route",
"run",
"subroute",
"urlFor",
"url_for",
)

View file

@ -0,0 +1,22 @@
"""
This type stub file was generated by pyright.
"""
__all__ = (
"EarlyExit",
"IDependencyInjector",
"IKleinRequest",
"IRequestLifecycle",
"IRequiredParameter",
"ISession",
"ISessionProcurer",
"ISessionStore",
"ISimpleAccount",
"ISimpleAccountBinding",
"NoSuchSession",
"SessionMechanism",
"TooLateForCookies",
"TransactionEnded",
"ValidationError",
"ValueAbsent",
)

View file

@ -0,0 +1,44 @@
"""
This type stub file was generated by pyright.
"""
from typing import Any, Callable, Union
from ._resource import KleinResource as _KleinResource
"""
This module, L{klein.resource}, serves two purposes:
- It's the global C{resource()} method on the global L{klein.Klein}
application.
- It's the module where L{KleinResource} is defined.
"""
KleinResource = _KleinResource
class _SpecialModuleObject:
"""
See the test in
L{klein.test.test_resource.GlobalAppTests.test_weird_resource_situation}
for an explanation.
"""
__all__ = ("KleinResource", "ensure_utf8_bytes")
KleinResource = _KleinResource
def __init__(self, preserve: Any) -> None: ...
@property
def ensure_utf8_bytes(self) -> Callable[[Union[str, bytes]], bytes]: ...
def __call__(self) -> _KleinResource:
"""
Return an L{IResource} which suitably wraps this app.
@returns: An L{IResource}
"""
...
def __repr__(self) -> str:
"""
Give a special C{repr()} to make the dual purpose of this object clear.
"""
...
module = ...

View file

@ -0,0 +1,3 @@
"""
This type stub file was generated by pyright.
"""

View file

@ -0,0 +1,9 @@
"""
This type stub file was generated by pyright.
"""
from hypothesis import HealthCheck, settings
"""
Tests for L{klein}.
"""

View file

@ -0,0 +1,130 @@
import base64
import os
from collections.abc import Generator
from typing import IO, Iterable, Tuple, Union
from zope.interface import Interface
from _typeshed import Incomplete
from twisted.python.compat import cmp as cmp, comparable as comparable
from twisted.python.runtime import platform as platform
from twisted.python.util import FancyEqMixin as FancyEqMixin
from twisted.python.win32 import (
ERROR_DIRECTORY as ERROR_DIRECTORY,
ERROR_FILE_NOT_FOUND as ERROR_FILE_NOT_FOUND,
ERROR_INVALID_NAME as ERROR_INVALID_NAME,
ERROR_PATH_NOT_FOUND as ERROR_PATH_NOT_FOUND,
O_BINARY as O_BINARY,
)
islink: Incomplete
randomBytes = os.urandom
armor = base64.urlsafe_b64encode
class IFilePath(Interface):
sep: Incomplete
def child(name) -> None: ...
def open(mode: str = ...) -> None: ...
def changed() -> None: ...
def getsize() -> None: ...
def getModificationTime() -> None: ...
def getStatusChangeTime() -> None: ...
def getAccessTime() -> None: ...
def exists() -> bool: ...
def isdir() -> bool: ...
def isfile() -> bool: ...
def children() -> Iterable[IFilePath]: ...
def basename() -> None: ...
def parent() -> IFilePath: ...
def sibling(name) -> None: ...
class InsecurePath(Exception): ...
class LinkError(Exception): ...
class UnlistableError(OSError):
originalException: Incomplete
def __init__(self, originalException: OSError) -> None: ...
class AbstractFilePath:
def getContent(self) -> bytes: ...
def parents(self) -> Generator[Incomplete, None, None]: ...
def children(self) -> Iterable[IFilePath]: ...
def walk(
self, descend: Incomplete | None = ...
) -> Generator[Incomplete, None, None]: ...
def sibling(self, path): ...
def descendant(self, segments): ...
def segmentsFrom(self, ancestor): ...
def __hash__(self): ...
def getmtime(self): ...
def getatime(self): ...
def getctime(self): ...
class RWX(FancyEqMixin):
compareAttributes: Incomplete
read: Incomplete
write: Incomplete
execute: Incomplete
def __init__(self, readable, writable, executable) -> None: ...
def shorthand(self): ...
class Permissions(FancyEqMixin):
compareAttributes: Incomplete
def __init__(self, statModeInt) -> None: ...
def shorthand(self): ...
class FilePath(AbstractFilePath):
path: Union[bytes, str]
alwaysCreate: Incomplete
def __init__(self, path: Union[bytes, str], alwaysCreate: bool = ...) -> None: ...
@property
def sep(self): ...
def asBytesMode(self, encoding: Incomplete | None = ...): ...
def asTextMode(self, encoding: Incomplete | None = ...): ...
def child(self, path: Union[str, bytes]) -> FilePath: ...
def preauthChild(self, path): ...
def childSearchPreauth(self, *paths): ...
def siblingExtensionSearch(self, *exts) -> FilePath: ...
def realpath(self): ...
def siblingExtension(self, ext: Union[str, bytes]) -> FilePath: ...
def linkTo(self, linkFilePath) -> None: ...
def open(self, mode: str = ...) -> IO[bytes]: ...
def restat(self, reraise: bool = ...) -> None: ...
def changed(self) -> None: ...
def chmod(self, mode: int) -> None: ...
def getsize(self): ...
def getModificationTime(self): ...
def getStatusChangeTime(self): ...
def getAccessTime(self): ...
def getInodeNumber(self): ...
def getDevice(self): ...
def getNumberOfHardLinks(self): ...
def getUserID(self): ...
def getGroupID(self): ...
def getPermissions(self): ...
def exists(self) -> bool: ...
def isdir(self) -> bool: ...
def isfile(self) -> bool: ...
def isBlockDevice(self): ...
def isSocket(self): ...
def islink(self): ...
def isabs(self): ...
def listdir(self): ...
def splitext(self) -> Tuple[str, str]: ...
def touch(self) -> None: ...
def remove(self) -> None: ...
def makedirs(self, ignoreExistingDirectory: bool = ...) -> None: ...
def globChildren(self, pattern): ...
def basename(self) -> str: ...
def dirname(self): ...
def parent(self) -> FilePath: ...
def setContent(self, content: bytes, ext: bytes = ...) -> None: ...
def __cmp__(self, other): ...
def createDirectory(self) -> None: ...
def requireCreate(self, val: int = ...) -> None: ...
def create(self) -> IO[bytes]: ...
def temporarySibling(self, extension: bytes = ...) -> FilePath: ...
def copyTo(self, destination, followLinks: bool = ...) -> None: ...
def moveTo(
self, destination: Union[str, bytes, FilePath], followLinks: bool = ...
) -> None: ...