mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-22 15:08:12 +01:00
Merge pull request #45 from craftbeerpi/development_fermentersteps
Transfer from organization dev branch that has now fermentersteps
This commit is contained in:
commit
f3cd341813
4157 changed files with 409 additions and 1598781 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -10,7 +10,9 @@ venv
|
||||||
cbpi/extension/ui
|
cbpi/extension/ui
|
||||||
node_modules
|
node_modules
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
|
.venv*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
.vscode/
|
||||||
config/*
|
config/*
|
||||||
|
|
BIN
cbpi/.DS_Store
vendored
BIN
cbpi/.DS_Store
vendored
Binary file not shown.
|
@ -1 +1 @@
|
||||||
__version__ = "4.0.2.0.a3"
|
__version__ = "4.0.2.0.a17"
|
||||||
|
|
|
@ -162,8 +162,7 @@ class FermenterStep:
|
||||||
props: Props = Props()
|
props: Props = Props()
|
||||||
type: str = None
|
type: str = None
|
||||||
status: StepState = StepState.INITIAL
|
status: StepState = StepState.INITIAL
|
||||||
#Add end data as unixtime to setp when active
|
endtime: int = 0 # endtime if step is active and timer is running
|
||||||
#step_end: float = 0
|
|
||||||
instance: str = None
|
instance: str = None
|
||||||
step: dict = None
|
step: dict = None
|
||||||
|
|
||||||
|
@ -171,7 +170,7 @@ class FermenterStep:
|
||||||
return "name={} props={}, type={}, instance={}".format(self.name, self.props, self.type, self.instance)
|
return "name={} props={}, type={}, instance={}".format(self.name, self.props, self.type, self.instance)
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
msg = self.instance.summary if self.instance is not None else ""
|
msg = self.instance.summary if self.instance is not None else ""
|
||||||
return dict(id=self.id, name=self.name, state_text=msg, type=self.type, status=self.status.value, props=self.props.to_dict())
|
return dict(id=self.id, name=self.name, state_text=msg, type=self.type, status=self.status.value, endtime=self.endtime, props=self.props.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -117,15 +117,6 @@ class CBPiStep(CBPiBase):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "name={} props={}, type={}".format(self.name, self.props, self.__class__.__name__)
|
return "name={} props={}, type={}".format(self.name, self.props, self.__class__.__name__)
|
||||||
|
|
||||||
#class CBPiFermentationStep(CBPiStep):
|
|
||||||
|
|
||||||
# def __init__(self, cbpi, fermenter, step, props, on_done) -> None:
|
|
||||||
# self.fermenter = fermenter
|
|
||||||
# id = step.get("id")
|
|
||||||
# name=step.get("name")
|
|
||||||
# self.step=step
|
|
||||||
# super().__init__(cbpi, id, name, props, on_done)
|
|
||||||
|
|
||||||
class CBPiFermentationStep(CBPiBase):
|
class CBPiFermentationStep(CBPiBase):
|
||||||
|
|
||||||
def __init__(self, cbpi, fermenter, step, props, on_done) -> None:
|
def __init__(self, cbpi, fermenter, step, props, on_done) -> None:
|
||||||
|
@ -136,6 +127,7 @@ class CBPiFermentationStep(CBPiBase):
|
||||||
self.timer = None
|
self.timer = None
|
||||||
self._done_callback = on_done
|
self._done_callback = on_done
|
||||||
self.props = props
|
self.props = props
|
||||||
|
self.endtime = int(step.get("endtime"))
|
||||||
self.cancel_reason: StepResult = None
|
self.cancel_reason: StepResult = None
|
||||||
self.summary = ""
|
self.summary = ""
|
||||||
self.task = None
|
self.task = None
|
||||||
|
@ -187,8 +179,11 @@ class CBPiFermentationStep(CBPiBase):
|
||||||
async def on_props_update(self, props):
|
async def on_props_update(self, props):
|
||||||
self.props = {**self.props, **props}
|
self.props = {**self.props, **props}
|
||||||
|
|
||||||
|
async def update_endtime(self):
|
||||||
|
await self.cbpi.fermenter.update_endtime(self.fermenter.id, self.id, self.endtime)
|
||||||
|
|
||||||
async def save_props(self):
|
async def save_props(self):
|
||||||
await self.cbpi.step.save()
|
self.cbpi.fermenter.save()
|
||||||
|
|
||||||
async def push_update(self):
|
async def push_update(self):
|
||||||
self.cbpi.fermenter.push_update(self.update_key)
|
self.cbpi.fermenter.push_update(self.update_key)
|
||||||
|
|
320
cbpi/cli.py
320
cbpi/cli.py
|
@ -12,15 +12,19 @@ from cbpi.craftbeerpi import CraftBeerPi
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import pkgutil
|
||||||
import shutil
|
import shutil
|
||||||
import yaml
|
import yaml
|
||||||
import click
|
import click
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
import zipfile
|
import zipfile
|
||||||
from importlib_metadata import version, metadata
|
from colorama import Fore, Back, Style
|
||||||
|
from importlib import import_module
|
||||||
|
import importlib
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
from importlib_metadata import metadata, version
|
||||||
|
from tabulate import tabulate
|
||||||
|
from PyInquirer import prompt, print_json
|
||||||
|
|
||||||
def create_config_file():
|
def create_config_file():
|
||||||
if os.path.exists(os.path.join(".", 'config', "config.yaml")) is False:
|
if os.path.exists(os.path.join(".", 'config', "config.yaml")) is False:
|
||||||
|
@ -115,7 +119,7 @@ def clear_db():
|
||||||
import os.path
|
import os.path
|
||||||
if os.path.exists(os.path.join(".", "craftbeerpi.db")) is True:
|
if os.path.exists(os.path.join(".", "craftbeerpi.db")) is True:
|
||||||
os.remove(os.path.join(".", "craftbeerpi.db"))
|
os.remove(os.path.join(".", "craftbeerpi.db"))
|
||||||
print("database Cleared")
|
print("database cleared")
|
||||||
|
|
||||||
def recursive_chown(path, owner, group):
|
def recursive_chown(path, owner, group):
|
||||||
for dirpath, dirnames, filenames in os.walk(path):
|
for dirpath, dirnames, filenames in os.walk(path):
|
||||||
|
@ -185,156 +189,47 @@ def check_for_setup():
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def plugins_add(package_name):
|
|
||||||
if package_name is None:
|
|
||||||
print("Pleaes provide a plugin Name")
|
|
||||||
return
|
|
||||||
|
|
||||||
if package_name == 'autostart':
|
|
||||||
print("Add craftbeerpi.service to systemd")
|
|
||||||
try:
|
|
||||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
|
||||||
srcfile = os.path.join(".", "config", "craftbeerpi.service")
|
|
||||||
destfile = os.path.join("/etc/systemd/system")
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
print("Copied craftbeerpi.service to /etc/systemd/system")
|
|
||||||
os.system('systemctl enable craftbeerpi.service')
|
|
||||||
print('Enabled craftbeerpi service')
|
|
||||||
os.system('systemctl start craftbeerpi.service')
|
|
||||||
print('Started craftbeerpi.service')
|
|
||||||
else:
|
|
||||||
print("craftbeerpi.service is already located in /etc/systemd/system")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
if package_name == 'chromium':
|
|
||||||
print("Add chromium.desktop to /etc/xdg/autostart/")
|
|
||||||
try:
|
|
||||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
|
||||||
srcfile = os.path.join(".", "config", "chromium.desktop")
|
|
||||||
destfile = os.path.join("/etc/xdg/autostart/")
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
|
||||||
else:
|
|
||||||
print("chromium.desktop is already located in /etc/xdg/autostart/")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
installation = True
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
p_metadata= metadata(package_name)
|
|
||||||
p_name=p_metadata['Name']
|
|
||||||
#if p_name != package_name:
|
|
||||||
# print("Error. Package name {} does not exist. Did you mean {}".format(package_name,p_name))
|
|
||||||
# installation = False
|
|
||||||
except Exception as e:
|
|
||||||
print("Error. Package {} cannot be found in installed packages".format(package_name))
|
|
||||||
installation = False
|
|
||||||
if installation:
|
|
||||||
with open(os.path.join(".", 'config', "config.yaml"), 'rt') as f:
|
|
||||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
|
||||||
if package_name in data["plugins"]:
|
|
||||||
print("")
|
|
||||||
print("Plugin {} already active".format(package_name))
|
|
||||||
print("")
|
|
||||||
return
|
|
||||||
data["plugins"].append(package_name)
|
|
||||||
with open(os.path.join(".", 'config', "config.yaml"), 'w') as outfile:
|
|
||||||
yaml.dump(data, outfile, default_flow_style=False)
|
|
||||||
print("")
|
|
||||||
print("Plugin {} activated".format(package_name))
|
|
||||||
print("")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def plugin_remove(package_name):
|
|
||||||
if package_name is None:
|
|
||||||
print("Pleaes provide a plugin Name")
|
|
||||||
return
|
|
||||||
|
|
||||||
if package_name == 'autostart':
|
|
||||||
print("Remove craftbeerpi.service from systemd")
|
|
||||||
try:
|
|
||||||
status = os.popen('systemctl list-units --type=service --state=running | grep craftbeerpi.service').read()
|
|
||||||
if status.find("craftbeerpi.service") != -1:
|
|
||||||
os.system('systemctl stop craftbeerpi.service')
|
|
||||||
print('Stopped craftbeerpi service')
|
|
||||||
os.system('systemctl disable craftbeerpi.service')
|
|
||||||
print('Removed craftbeerpi.service as service')
|
|
||||||
else:
|
|
||||||
print('craftbeerpi.service service is not running')
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
|
||||||
os.remove(os.path.join("/etc/systemd/system","craftbeerpi.service"))
|
|
||||||
print("Deleted craftbeerpi.service from /etc/systemd/system")
|
|
||||||
else:
|
|
||||||
print("craftbeerpi.service is not located in /etc/systemd/system")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
if package_name == 'chromium':
|
|
||||||
print("Remove chromium.desktop from /etc/xdg/autostart/")
|
|
||||||
try:
|
|
||||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
|
||||||
os.remove(os.path.join("/etc/xdg/autostart/","chromium.desktop"))
|
|
||||||
print("Deleted chromium.desktop from /etc/xdg/autostart/")
|
|
||||||
else:
|
|
||||||
print("chromium.desktop is not located in /etc/xdg/autostart/")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(os.path.join(".", 'config', "config.yaml"), 'rt') as f:
|
|
||||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
|
||||||
|
|
||||||
data["plugins"] = list(filter(lambda k: package_name not in k, data["plugins"]))
|
|
||||||
with open(os.path.join(".", 'config', "config.yaml"), 'w') as outfile:
|
|
||||||
yaml.dump(data, outfile, default_flow_style=False)
|
|
||||||
print("")
|
|
||||||
print("Plugin {} deactivated".format(package_name))
|
|
||||||
print("")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def plugins_list():
|
def plugins_list():
|
||||||
print("--------------------------------------")
|
result = []
|
||||||
print("List of active plugins")
|
print("")
|
||||||
try:
|
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
|
||||||
with open(os.path.join(".", 'config', "config.yaml"), 'rt') as f:
|
print("")
|
||||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
discovered_plugins = {
|
||||||
|
name: importlib.import_module(name)
|
||||||
|
for finder, name, ispkg
|
||||||
|
in pkgutil.iter_modules()
|
||||||
|
if name.startswith('cbpi') and len(name) > 4
|
||||||
|
}
|
||||||
|
for key, module in discovered_plugins.items():
|
||||||
|
try:
|
||||||
|
meta = metadata(key)
|
||||||
|
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
|
||||||
|
|
||||||
for p in data["plugins"]:
|
except Exception as e:
|
||||||
try:
|
print(e)
|
||||||
p_metadata= metadata(p)
|
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
|
||||||
p_Homepage= p_metadata['Home-page']
|
|
||||||
p_version = p_metadata['Version']
|
|
||||||
p_Author = p_metadata['Author']
|
|
||||||
print("- ({})\t{}".format(p_version,p))
|
|
||||||
except Exception as e:
|
|
||||||
print (e)
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
pass
|
|
||||||
print("--------------------------------------")
|
|
||||||
|
|
||||||
|
|
||||||
def plugin_create(name):
|
|
||||||
|
def plugin_create():
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("Plugin Creation")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
'type': 'input',
|
||||||
|
'name': 'name',
|
||||||
|
'message': 'Plugin Name:',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
answers = prompt(questions)
|
||||||
|
|
||||||
|
name = "cbpi4_" + answers["name"]
|
||||||
if os.path.exists(os.path.join(".", name)) is True:
|
if os.path.exists(os.path.join(".", name)) is True:
|
||||||
print("Cant create Plugin. Folder {} already exists ".format(name))
|
print("Cant create Plugin. Folder {} already exists ".format(name))
|
||||||
return
|
return
|
||||||
|
@ -373,19 +268,25 @@ def plugin_create(name):
|
||||||
|
|
||||||
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
|
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
|
||||||
fh.write(outputText)
|
fh.write(outputText)
|
||||||
|
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("")
|
print("")
|
||||||
print(
|
print("Plugin {}{}{} created! ".format(Fore.LIGHTGREEN_EX, name, Style.RESET_ALL) )
|
||||||
"Plugin {} created! See https://openbrewing.gitbook.io/craftbeerpi4_support/readme/development how to run your plugin ".format(
|
|
||||||
name))
|
|
||||||
print("")
|
print("")
|
||||||
print("Happy Development! Cheers")
|
print("Developer Documentation: https://openbrewing.gitbook.io/craftbeerpi4_support/readme/development")
|
||||||
|
print("")
|
||||||
|
print("Happy developing! Cheers")
|
||||||
print("")
|
print("")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
def main():
|
def main():
|
||||||
|
print("---------------------")
|
||||||
|
print("Welcome to CBPi")
|
||||||
|
print("---------------------")
|
||||||
level = logging.INFO
|
level = logging.INFO
|
||||||
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
pass
|
pass
|
||||||
|
@ -413,9 +314,10 @@ def onewire(list, setup):
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def start():
|
def start():
|
||||||
|
'''Lets go brewing'''
|
||||||
if check_for_setup() is False:
|
if check_for_setup() is False:
|
||||||
return
|
return
|
||||||
print("START")
|
print("Starting up CraftBeerPi ...")
|
||||||
cbpi = CraftBeerPi()
|
cbpi = CraftBeerPi()
|
||||||
cbpi.start()
|
cbpi.start()
|
||||||
|
|
||||||
|
@ -427,31 +329,105 @@ def plugins():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.argument('name')
|
|
||||||
def add(name):
|
|
||||||
'''Activate Plugin, autostart or chromium '''
|
|
||||||
plugins_add(name)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument('name')
|
def create():
|
||||||
def remove(name):
|
|
||||||
'''Deactivate Plugin, autostart or chromium'''
|
|
||||||
plugin_remove(name)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.argument('name')
|
|
||||||
def create(name):
|
|
||||||
'''Create New Plugin'''
|
'''Create New Plugin'''
|
||||||
plugin_create(name)
|
plugin_create()
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument('name')
|
||||||
|
def autostart(name):
|
||||||
|
'''(on|off|status) Enable or disable autostart'''
|
||||||
|
if(name == "status"):
|
||||||
|
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
||||||
|
print("CraftBeerPi Autostart is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
|
||||||
|
else:
|
||||||
|
print("CraftBeerPi Autostart is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||||
|
elif(name == "on"):
|
||||||
|
print("Add craftbeerpi.service to systemd")
|
||||||
|
try:
|
||||||
|
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
||||||
|
srcfile = os.path.join(".", "config", "craftbeerpi.service")
|
||||||
|
destfile = os.path.join("/etc/systemd/system")
|
||||||
|
shutil.copy(srcfile, destfile)
|
||||||
|
print("Copied craftbeerpi.service to /etc/systemd/system")
|
||||||
|
os.system('systemctl enable craftbeerpi.service')
|
||||||
|
print('Enabled craftbeerpi service')
|
||||||
|
os.system('systemctl start craftbeerpi.service')
|
||||||
|
print('Started craftbeerpi.service')
|
||||||
|
else:
|
||||||
|
print("craftbeerpi.service is already located in /etc/systemd/system")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
return
|
||||||
|
elif(name == "off"):
|
||||||
|
print("Remove craftbeerpi.service from systemd")
|
||||||
|
try:
|
||||||
|
status = os.popen('systemctl list-units --type=service --state=running | grep craftbeerpi.service').read()
|
||||||
|
if status.find("craftbeerpi.service") != -1:
|
||||||
|
os.system('systemctl stop craftbeerpi.service')
|
||||||
|
print('Stopped craftbeerpi service')
|
||||||
|
os.system('systemctl disable craftbeerpi.service')
|
||||||
|
print('Removed craftbeerpi.service as service')
|
||||||
|
else:
|
||||||
|
print('craftbeerpi.service service is not running')
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
||||||
|
os.remove(os.path.join("/etc/systemd/system","craftbeerpi.service"))
|
||||||
|
print("Deleted craftbeerpi.service from /etc/systemd/system")
|
||||||
|
else:
|
||||||
|
print("craftbeerpi.service is not located in /etc/systemd/system")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument('name')
|
||||||
|
def chromium(name):
|
||||||
|
'''(on|off|status) Enable or disable Kiosk mode'''
|
||||||
|
if(name == "status"):
|
||||||
|
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
||||||
|
print("CraftBeerPi Chromium Desktop is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
|
||||||
|
else:
|
||||||
|
print("CraftBeerPi Chromium Desktop is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||||
|
elif(name == "on"):
|
||||||
|
print("Add chromium.desktop to /etc/xdg/autostart/")
|
||||||
|
try:
|
||||||
|
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
||||||
|
srcfile = os.path.join(".", "config", "chromium.desktop")
|
||||||
|
destfile = os.path.join("/etc/xdg/autostart/")
|
||||||
|
shutil.copy(srcfile, destfile)
|
||||||
|
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
||||||
|
else:
|
||||||
|
print("chromium.desktop is already located in /etc/xdg/autostart/")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
return
|
||||||
|
elif(name == "off"):
|
||||||
|
print("Remove chromium.desktop from /etc/xdg/autostart/")
|
||||||
|
try:
|
||||||
|
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
||||||
|
os.remove(os.path.join("/etc/xdg/autostart/","chromium.desktop"))
|
||||||
|
print("Deleted chromium.desktop from /etc/xdg/autostart/")
|
||||||
|
else:
|
||||||
|
print("chromium.desktop is not located in /etc/xdg/autostart/")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
main.add_command(setup)
|
main.add_command(setup)
|
||||||
main.add_command(start)
|
main.add_command(start)
|
||||||
|
main.add_command(autostart)
|
||||||
|
main.add_command(chromium)
|
||||||
main.add_command(plugins)
|
main.add_command(plugins)
|
||||||
main.add_command(onewire)
|
main.add_command(onewire)
|
||||||
main.add_command(add)
|
|
||||||
main.add_command(remove)
|
|
||||||
main.add_command(create)
|
main.add_command(create)
|
||||||
|
|
|
@ -22,7 +22,7 @@ class BasicController:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.data = []
|
self.data = []
|
||||||
self.autostart = True
|
self.autostart = True
|
||||||
self._loop = asyncio.get_event_loop()
|
#self._loop = asyncio.get_event_loop()
|
||||||
self.path = os.path.join(".", 'config', file)
|
self.path = os.path.join(".", 'config', file)
|
||||||
self.cbpi.app.on_cleanup.append(self.shutdown)
|
self.cbpi.app.on_cleanup.append(self.shutdown)
|
||||||
|
|
||||||
|
@ -100,7 +100,8 @@ class BasicController:
|
||||||
|
|
||||||
await item.instance.start()
|
await item.instance.start()
|
||||||
item.instance.running = True
|
item.instance.running = True
|
||||||
item.instance.task = self._loop.create_task(item.instance._run())
|
item.instance.task = asyncio.get_event_loop().create_task(item.instance._run())
|
||||||
|
#item.instance.task = self._loop.create_task(item.instance._run())
|
||||||
|
|
||||||
logging.info("{} started {}".format(self.name, id))
|
logging.info("{} started {}".format(self.name, id))
|
||||||
|
|
||||||
|
|
|
@ -16,78 +16,6 @@ from tabulate import tabulate
|
||||||
import sys, os
|
import sys, os
|
||||||
from ..api.step import CBPiStep, StepMove, StepResult, StepState, CBPiFermentationStep
|
from ..api.step import CBPiStep, StepMove, StepResult, StepState, CBPiFermentationStep
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FermentStep:
|
|
||||||
|
|
||||||
def __init__(self, cbpi, step, on_done) -> None:
|
|
||||||
self.cbpi = cbpi
|
|
||||||
self.logger = logging.getLogger(__name__)
|
|
||||||
self.step = step
|
|
||||||
self.props = step.props
|
|
||||||
self._done_callback = on_done
|
|
||||||
self.task = None
|
|
||||||
self.summary = ""
|
|
||||||
|
|
||||||
def _done(self, task):
|
|
||||||
if self._done_callback is not None:
|
|
||||||
try:
|
|
||||||
result = task.result()
|
|
||||||
self._done_callback(self, result)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(e)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def run(self):
|
|
||||||
while self.running:
|
|
||||||
logging.info(self.step)
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _run(self):
|
|
||||||
try:
|
|
||||||
await self.step.instance.on_start()
|
|
||||||
await self.step.instance.run()
|
|
||||||
#await self.on_start()
|
|
||||||
#await self.run()
|
|
||||||
self.cancel_reason = StepResult.DONE
|
|
||||||
except asyncio.CancelledError as e:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
await self.on_stop()
|
|
||||||
|
|
||||||
return self.cancel_reason
|
|
||||||
|
|
||||||
async def start(self):
|
|
||||||
self.logger.info("Start {}".format(self.step.name))
|
|
||||||
self.running = True
|
|
||||||
self.task = asyncio.create_task(self._run())
|
|
||||||
self.task.add_done_callback(self._done)
|
|
||||||
|
|
||||||
async def next(self):
|
|
||||||
self.running = False
|
|
||||||
self.cancel_reason = StepResult.NEXT
|
|
||||||
self.task.cancel()
|
|
||||||
await self.task
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
try:
|
|
||||||
self.running = False
|
|
||||||
if self.task is not None and self.task.done() is False:
|
|
||||||
self.logger.info("Stopping Task")
|
|
||||||
self.cancel_reason = StepResult.STOP
|
|
||||||
self.task.cancel()
|
|
||||||
await self.task
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(e)
|
|
||||||
|
|
||||||
async def on_start(self):
|
|
||||||
#self.props.hello = "WOOHOo"
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def on_stop(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class FermentationController:
|
class FermentationController:
|
||||||
|
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
|
@ -95,7 +23,6 @@ class FermentationController:
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.path = os.path.join(".", 'config', "fermenter_data.json")
|
self.path = os.path.join(".", 'config', "fermenter_data.json")
|
||||||
self._loop = asyncio.get_event_loop()
|
|
||||||
self.data = []
|
self.data = []
|
||||||
self.types = {}
|
self.types = {}
|
||||||
self.steptypes = {}
|
self.steptypes = {}
|
||||||
|
@ -127,6 +54,10 @@ class FermentationController:
|
||||||
for step in fermenter.steps:
|
for step in fermenter.steps:
|
||||||
try:
|
try:
|
||||||
self.logger.info("Stop {}".format(step.name))
|
self.logger.info("Stop {}".format(step.name))
|
||||||
|
try:
|
||||||
|
step.instance.shutdown = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
await step.instance.stop()
|
await step.instance.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
|
@ -136,15 +67,16 @@ class FermentationController:
|
||||||
for step in fermenter.steps:
|
for step in fermenter.steps:
|
||||||
try:
|
try:
|
||||||
self.logger.info("Stop {}".format(step.name))
|
self.logger.info("Stop {}".format(step.name))
|
||||||
|
try:
|
||||||
|
step.instance.shutdown = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
await step.instance.stop()
|
await step.instance.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
|
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
# if os.path.exists(self.path) is False:
|
|
||||||
# with open(self.path, "w") as file:
|
|
||||||
# json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
|
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
|
|
||||||
|
@ -155,7 +87,14 @@ class FermentationController:
|
||||||
id = item.get("id")
|
id = item.get("id")
|
||||||
name = item.get("name")
|
name = item.get("name")
|
||||||
props = Props(item.get("props"))
|
props = Props(item.get("props"))
|
||||||
|
try:
|
||||||
|
endtime = int(item.get("endtime", 0))
|
||||||
|
except:
|
||||||
|
endtime=0
|
||||||
|
|
||||||
status = StepState(item.get("status", "I"))
|
status = StepState(item.get("status", "I"))
|
||||||
|
if status == StepState.ACTIVE:
|
||||||
|
status = StepState("S")
|
||||||
type = item.get("type")
|
type = item.get("type")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -166,7 +105,7 @@ class FermentationController:
|
||||||
logging.warning("Failed to create step instance %s - %s" % (id, e))
|
logging.warning("Failed to create step instance %s - %s" % (id, e))
|
||||||
instance = None
|
instance = None
|
||||||
|
|
||||||
step = FermenterStep(id=id, name=name, fermenter=fermenter, props=props, type=type, status=status, instance=instance)
|
step = FermenterStep(id=id, name=name, fermenter=fermenter, props=props, type=type, status=status, endtime=endtime, instance=instance)
|
||||||
return step
|
return step
|
||||||
|
|
||||||
def _done(self, step_instance, result, fermenter):
|
def _done(self, step_instance, result, fermenter):
|
||||||
|
@ -215,7 +154,6 @@ class FermentationController:
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
if self.data == []:
|
if self.data == []:
|
||||||
#logging.info(self.data)
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return {"data": list(map(lambda x: x.to_dict(), self.data)), "types":self.get_types(), "steptypes":self.get_steptypes()}
|
return {"data": list(map(lambda x: x.to_dict(), self.data)), "types":self.get_types(), "steptypes":self.get_steptypes()}
|
||||||
|
@ -232,7 +170,6 @@ class FermentationController:
|
||||||
|
|
||||||
def get_fermenter_steps(self):
|
def get_fermenter_steps(self):
|
||||||
if self.data == []:
|
if self.data == []:
|
||||||
#logging.info(self.data)
|
|
||||||
pass
|
pass
|
||||||
fermentersteps=[]
|
fermentersteps=[]
|
||||||
steplist=list(map(lambda x: x.to_dict(), self.data))
|
steplist=list(map(lambda x: x.to_dict(), self.data))
|
||||||
|
@ -301,16 +238,16 @@ class FermentationController:
|
||||||
with open(self.path, "w") as file:
|
with open(self.path, "w") as file:
|
||||||
json.dump(data, file, indent=4, sort_keys=True)
|
json.dump(data, file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
async def create_step(self, id, item):
|
def create_step(self, id, item):
|
||||||
try:
|
try:
|
||||||
stepid = shortuuid.uuid()
|
stepid = shortuuid.uuid()
|
||||||
props = item.get("props")
|
item['id'] = stepid
|
||||||
status = StepState("I")
|
status = StepState("I")
|
||||||
type = item.get("type")
|
type = item.get("type")
|
||||||
name = item.get("name")
|
name = item.get("name")
|
||||||
|
endtime = item.get("endtime", 0)
|
||||||
props = Props(item.get("props"))
|
props = Props(item.get("props"))
|
||||||
fermenter = self._find_by_id(id)
|
fermenter = self._find_by_id(id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
type_cfg = self.steptypes.get(type)
|
type_cfg = self.steptypes.get(type)
|
||||||
clazz = type_cfg.get("class")
|
clazz = type_cfg.get("class")
|
||||||
|
@ -318,13 +255,8 @@ class FermentationController:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("Failed to create step instance %s - %s" % (id, e))
|
logging.warning("Failed to create step instance %s - %s" % (id, e))
|
||||||
instance = None
|
instance = None
|
||||||
step = FermenterStep(id=stepid, name=name, fermenter=fermenter, props=props, type=type, status=status, instance=instance)
|
step = FermenterStep(id=stepid, name=name, fermenter=fermenter, props=props, type=type, status=status, endtime=endtime, instance=instance)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fermenter.steps.append(step)
|
|
||||||
self.save()
|
|
||||||
self.push_update("fermenterstepupdate")
|
|
||||||
return step
|
return step
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
|
@ -335,30 +267,29 @@ class FermentationController:
|
||||||
props = item.get("props")
|
props = item.get("props")
|
||||||
status = StepState("I")
|
status = StepState("I")
|
||||||
type = item.get("type")
|
type = item.get("type")
|
||||||
#logging.info(type)
|
endtime = 0
|
||||||
name = item.get("name")
|
name = item.get("name")
|
||||||
props = Props(item.get("props"))
|
props = Props(item.get("props"))
|
||||||
|
|
||||||
logging.info("update step")
|
logging.info("update step")
|
||||||
try:
|
try:
|
||||||
type_cfg = self.steptypes.get(type)
|
type_cfg = self.steptypes.get(type)
|
||||||
#logging.info(type_cfg)
|
logging.info(type_cfg)
|
||||||
clazz = type_cfg.get("class")
|
clazz = type_cfg.get("class")
|
||||||
#logging.info(clazz)
|
logging.info(clazz)
|
||||||
instance = clazz(self.cbpi, fermenter, item, props, self._done)
|
instance = clazz(self.cbpi, fermenter, item, props, self._done)
|
||||||
|
logging.info(instance)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("Failed to create step instance %s - %s " % (item.id, e))
|
logging.warning("Failed to create step instance %s - %s " % (item.id, e))
|
||||||
instance = None
|
instance = None
|
||||||
step = FermenterStep(id=stepid, name=name, fermenter=fermenter, props=props, type=type, status=status, instance=instance)
|
step = FermenterStep(id=stepid, name=name, fermenter=fermenter, props=props, type=type, status=status, endtime=endtime, instance=instance)
|
||||||
#logging.info(step)
|
|
||||||
#logging.info(fermenter.steps)
|
|
||||||
try:
|
try:
|
||||||
fermenter.steps = list(map(lambda old: step if old.id == step.id else old, fermenter.steps))
|
fermenter.steps = list(map(lambda old: step if old.id == step.id else old, fermenter.steps))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(e)
|
logging.info(e)
|
||||||
#logging.info(fermenter.steps)
|
|
||||||
self.save()
|
self.save()
|
||||||
#logging.info("SAVEUPDATE")
|
|
||||||
|
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
|
@ -376,6 +307,15 @@ class FermentationController:
|
||||||
self.save()
|
self.save()
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
|
async def update_endtime(self, id, stepid, endtime):
|
||||||
|
try:
|
||||||
|
item = self._find_by_id(id)
|
||||||
|
step = self._find_step_by_id(item.steps, stepid)
|
||||||
|
step.endtime = int(endtime)
|
||||||
|
self.save()
|
||||||
|
self.push_update("fermenterstepupdate")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(e)
|
||||||
|
|
||||||
def _find_by_status(self, data, status):
|
def _find_by_status(self, data, status):
|
||||||
return next((item for item in data if item.status == status), None)
|
return next((item for item in data if item.status == status), None)
|
||||||
|
@ -395,10 +335,12 @@ class FermentationController:
|
||||||
|
|
||||||
step = self._find_by_status(item.steps, StepState.STOP)
|
step = self._find_by_status(item.steps, StepState.STOP)
|
||||||
if step is not None:
|
if step is not None:
|
||||||
|
endtime = step.endtime
|
||||||
await step.instance.start()
|
await step.instance.start()
|
||||||
logging.info("Restarting step {}".format(step.name))
|
logging.info("Restarting step {}".format(step.name))
|
||||||
step.status = StepState.ACTIVE
|
step.status = StepState.ACTIVE
|
||||||
self.save()
|
self.save()
|
||||||
|
self.push_update()
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -407,10 +349,12 @@ class FermentationController:
|
||||||
if step is None:
|
if step is None:
|
||||||
self.logger.info("No futher step to start")
|
self.logger.info("No futher step to start")
|
||||||
else:
|
else:
|
||||||
|
step.instance.endtime = 0
|
||||||
await step.instance.start()
|
await step.instance.start()
|
||||||
logging.info("Starting step {}".format(step.name))
|
logging.info("Starting step {}".format(step.name))
|
||||||
step.status = StepState.ACTIVE
|
step.status = StepState.ACTIVE
|
||||||
self.save()
|
self.save()
|
||||||
|
self.push_update()
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -451,7 +395,8 @@ class FermentationController:
|
||||||
|
|
||||||
await item.instance.start()
|
await item.instance.start()
|
||||||
item.instance.running = True
|
item.instance.running = True
|
||||||
item.instance.task = self._loop.create_task(item.instance._run())
|
item.instance.task = asyncio.get_event_loop().create_task(item.instance._run())
|
||||||
|
|
||||||
|
|
||||||
logging.info("{} started {}".format(item.name, id))
|
logging.info("{} started {}".format(item.name, id))
|
||||||
|
|
||||||
|
@ -496,7 +441,7 @@ class FermentationController:
|
||||||
await self.start(id)
|
await self.start(id)
|
||||||
else:
|
else:
|
||||||
logging.info("No Step is running")
|
logging.info("No Step is running")
|
||||||
|
self.push_update()
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -511,10 +456,13 @@ class FermentationController:
|
||||||
self.logger.info("Stopping Step {} {}".format(step.name, step.id))
|
self.logger.info("Stopping Step {} {}".format(step.name, step.id))
|
||||||
try:
|
try:
|
||||||
await step.instance.stop()
|
await step.instance.stop()
|
||||||
|
await step.instance.reset()
|
||||||
step.status = StepState.INITIAL
|
step.status = StepState.INITIAL
|
||||||
|
step.endtime = 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
self.save()
|
self.save()
|
||||||
|
self.push_update()
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -533,6 +481,7 @@ class FermentationController:
|
||||||
|
|
||||||
fermenter.steps[index], fermenter.steps[index+direction] = fermenter.steps[index+direction], fermenter.steps[index]
|
fermenter.steps[index], fermenter.steps[index+direction] = fermenter.steps[index+direction], fermenter.steps[index]
|
||||||
self.save()
|
self.save()
|
||||||
|
self.push_update()
|
||||||
self.push_update("fermenterstepupdate")
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -542,16 +491,20 @@ class FermentationController:
|
||||||
|
|
||||||
if key == self.update_key:
|
if key == self.update_key:
|
||||||
self.cbpi.ws.send(dict(topic=key, data=list(map(lambda item: item.to_dict(), self.data))))
|
self.cbpi.ws.send(dict(topic=key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||||
#self.cbpi.push_update("cbpi/{}".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
|
||||||
|
|
||||||
for item in self.data:
|
for item in self.data:
|
||||||
self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), item.to_dict())
|
self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), item.to_dict())
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
#logging.info("FERMENTERSTEPUPDATE {}".format(key))
|
|
||||||
fermentersteps=self.get_fermenter_steps()
|
fermentersteps=self.get_fermenter_steps()
|
||||||
self.cbpi.ws.send(dict(topic=key, data=fermentersteps))
|
self.cbpi.ws.send(dict(topic=key, data=fermentersteps))
|
||||||
|
|
||||||
|
# send mqtt update for active femrentersteps
|
||||||
|
for fermenter in fermentersteps:
|
||||||
|
for step in fermenter['steps']:
|
||||||
|
if step['status'] == 'A':
|
||||||
|
self.cbpi.push_update("cbpi/{}/{}/{}".format(key,fermenter['id'],step['id']), step)
|
||||||
|
|
||||||
async def call_action(self, id, action, parameter) -> None:
|
async def call_action(self, id, action, parameter) -> None:
|
||||||
logging.info("FermenterStep Controller - call Action {} {}".format(id, action))
|
logging.info("FermenterStep Controller - call Action {} {}".format(id, action))
|
||||||
try:
|
try:
|
||||||
|
@ -561,7 +514,6 @@ class FermentationController:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("FermenterStep Controller - Failed to call action on {} {} {}".format(id, action, e))
|
logging.error("FermenterStep Controller - Failed to call action on {} {} {}".format(id, action, e))
|
||||||
|
|
||||||
# todo: Sensors may need to be removed when saving the recipe -> need to be replaced when assinging later to Fermenter with 'fermenter.sensor'
|
|
||||||
async def savetobook(self, fermenterid):
|
async def savetobook(self, fermenterid):
|
||||||
name = shortuuid.uuid()
|
name = shortuuid.uuid()
|
||||||
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
|
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
|
||||||
|
@ -569,7 +521,7 @@ class FermentationController:
|
||||||
try:
|
try:
|
||||||
brewname = fermenter.brewname
|
brewname = fermenter.brewname
|
||||||
description = fermenter.description
|
description = fermenter.description
|
||||||
# todo add escription at later point of time, once description has been added to fermenter dataclass
|
|
||||||
except:
|
except:
|
||||||
brewname = ""
|
brewname = ""
|
||||||
description = ""
|
description = ""
|
||||||
|
@ -583,20 +535,38 @@ class FermentationController:
|
||||||
with open(path, "w") as file:
|
with open(path, "w") as file:
|
||||||
yaml.dump(data, file)
|
yaml.dump(data, file)
|
||||||
|
|
||||||
async def load_recipe(self, data, fermenterid):
|
async def load_recipe(self, data, fermenterid, name):
|
||||||
try:
|
try:
|
||||||
await self.shutdown(None, fermenterid)
|
await self.shutdown(None, fermenterid)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
fermenter = self._find_by_id(fermenterid)
|
fermenter = self._find_by_id(fermenterid)
|
||||||
|
|
||||||
def add_runtime_data(item):
|
def add_runtime_data(item):
|
||||||
item["status"] = "I"
|
item["status"] = "I"
|
||||||
|
item["endtime"] = 0
|
||||||
item["id"] = shortuuid.uuid()
|
item["id"] = shortuuid.uuid()
|
||||||
item["props"]["Sensor"] = fermenter.sensor
|
item["props"]["Sensor"] = fermenter.sensor
|
||||||
|
|
||||||
list(map(lambda item: add_runtime_data(item), data.get("steps")))
|
list(map(lambda item: add_runtime_data(item), data.get("steps")))
|
||||||
fermenter.description = data['basic']['desc']
|
fermenter.description = data['basic']['desc']
|
||||||
fermenter.brewname = data['basic']['name']
|
if name is not None:
|
||||||
fermenter.steps=[]
|
fermenter.brewname = name
|
||||||
|
else:
|
||||||
|
fermenter.brewname = data['basic']['name']
|
||||||
await self.update(fermenter)
|
await self.update(fermenter)
|
||||||
|
fermenter.steps=[]
|
||||||
for item in data.get("steps"):
|
for item in data.get("steps"):
|
||||||
await self.create_step(fermenterid, item)
|
fermenter.steps.append(self.create_step(fermenterid, item))
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
self.push_update("fermenterstepupdate")
|
||||||
|
|
||||||
|
async def add_step(self, fermenterid, newstep):
|
||||||
|
fermenter = self._find_by_id(fermenterid)
|
||||||
|
step = self.create_step(fermenterid, newstep)
|
||||||
|
fermenter.steps.append(step)
|
||||||
|
self.save()
|
||||||
|
self.push_update("fermenterstepupdate")
|
||||||
|
return step
|
||||||
|
|
|
@ -69,13 +69,13 @@ class FermenterRecipeController:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
|
||||||
async def brew(self, name, fermenterid):
|
async def brew(self, recipeid, fermenterid, name):
|
||||||
|
|
||||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % name)
|
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % recipeid)
|
||||||
logging.info(recipe_path)
|
logging.info(recipe_path)
|
||||||
with open(recipe_path) as file:
|
with open(recipe_path) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
await self.cbpi.fermenter.load_recipe(data, fermenterid)
|
await self.cbpi.fermenter.load_recipe(data, fermenterid, name)
|
||||||
|
|
||||||
async def clone(self, id, new_name):
|
async def clone(self, id, new_name):
|
||||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % id)
|
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % id)
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
|
|
||||||
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from importlib_metadata import version, metadata
|
|
||||||
import datetime
|
|
||||||
import aiohttp
|
|
||||||
import asyncio
|
|
||||||
import yaml
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
from cbpi.utils.utils import load_config
|
from cbpi.utils.utils import load_config
|
||||||
|
from importlib_metadata import metadata, version
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PluginController():
|
class PluginController():
|
||||||
modules = {}
|
modules = {}
|
||||||
types = {}
|
types = {}
|
||||||
|
@ -23,7 +21,8 @@ class PluginController():
|
||||||
|
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
|
|
||||||
this_directory = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-1])
|
this_directory = os.sep.join(
|
||||||
|
os.path.abspath(__file__).split(os.sep)[:-1])
|
||||||
for filename in os.listdir(os.path.join(this_directory, "../extension")):
|
for filename in os.listdir(os.path.join(this_directory, "../extension")):
|
||||||
if os.path.isdir(
|
if os.path.isdir(
|
||||||
os.path.join(this_directory, "../extension/") + filename) is False or filename == "__pycache__":
|
os.path.join(this_directory, "../extension/") + filename) is False or filename == "__pycache__":
|
||||||
|
@ -47,16 +46,22 @@ class PluginController():
|
||||||
|
|
||||||
def load_plugins_from_evn(self):
|
def load_plugins_from_evn(self):
|
||||||
|
|
||||||
for p in self.cbpi.static_config.get("plugins", []):
|
discovered_plugins = {
|
||||||
|
name: importlib.import_module(name)
|
||||||
|
for finder, name, ispkg
|
||||||
|
in pkgutil.iter_modules()
|
||||||
|
if name.startswith('cbpi') and len(name) > 4
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in discovered_plugins.items():
|
||||||
|
from importlib.metadata import version
|
||||||
try:
|
try:
|
||||||
logger.info("Try to load plugin: %s " % p)
|
logger.info("Try to load plugin: {} == {} ".format(
|
||||||
self.modules[p] = import_module(p)
|
key, version(key)))
|
||||||
self.modules[p].setup(self.cbpi)
|
value.setup(self.cbpi)
|
||||||
|
logger.info("Plugin {} loaded successfully".format(key))
|
||||||
logger.info("Plugin %s loaded successfully" % p)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("FAILED to load plugin %s " % p)
|
logger.error("FAILED to load plugin {} ".format(key))
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
def register(self, name, clazz) -> None:
|
def register(self, name, clazz) -> None:
|
||||||
|
@ -75,7 +80,8 @@ class PluginController():
|
||||||
self.cbpi.kettle.types[name] = self._parse_step_props(clazz, name)
|
self.cbpi.kettle.types[name] = self._parse_step_props(clazz, name)
|
||||||
|
|
||||||
if issubclass(clazz, CBPiFermenterLogic):
|
if issubclass(clazz, CBPiFermenterLogic):
|
||||||
self.cbpi.fermenter.types[name] = self._parse_step_props(clazz, name)
|
self.cbpi.fermenter.types[name] = self._parse_step_props(
|
||||||
|
clazz, name)
|
||||||
|
|
||||||
if issubclass(clazz, CBPiSensor):
|
if issubclass(clazz, CBPiSensor):
|
||||||
self.cbpi.sensor.types[name] = self._parse_step_props(clazz, name)
|
self.cbpi.sensor.types[name] = self._parse_step_props(clazz, name)
|
||||||
|
@ -84,7 +90,8 @@ class PluginController():
|
||||||
self.cbpi.step.types[name] = self._parse_step_props(clazz, name)
|
self.cbpi.step.types[name] = self._parse_step_props(clazz, name)
|
||||||
|
|
||||||
if issubclass(clazz, CBPiFermentationStep):
|
if issubclass(clazz, CBPiFermentationStep):
|
||||||
self.cbpi.fermenter.steptypes[name] = self._parse_step_props(clazz, name)
|
self.cbpi.fermenter.steptypes[name] = self._parse_step_props(
|
||||||
|
clazz, name)
|
||||||
|
|
||||||
if issubclass(clazz, CBPiExtension):
|
if issubclass(clazz, CBPiExtension):
|
||||||
self.c = clazz(self.cbpi)
|
self.c = clazz(self.cbpi)
|
||||||
|
@ -123,7 +130,8 @@ class PluginController():
|
||||||
parameters = []
|
parameters = []
|
||||||
for p in method.__getattribute__("parameters"):
|
for p in method.__getattribute__("parameters"):
|
||||||
parameters.append(self._parse_property_object(p))
|
parameters.append(self._parse_property_object(p))
|
||||||
result["actions"].append({"method": method_name, "label": key, "parameters": parameters})
|
result["actions"].append(
|
||||||
|
{"method": method_name, "label": key, "parameters": parameters})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -185,27 +193,25 @@ class PluginController():
|
||||||
async def load_plugin_list(self):
|
async def load_plugin_list(self):
|
||||||
result = []
|
result = []
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(".", 'config', "config.yaml"), 'rt') as f:
|
discovered_plugins = {
|
||||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
name: importlib.import_module(name)
|
||||||
|
for finder, name, ispkg
|
||||||
|
in pkgutil.iter_modules()
|
||||||
|
if name.startswith('cbpi') and len(name) > 4
|
||||||
|
}
|
||||||
|
for key, module in discovered_plugins.items():
|
||||||
|
from importlib.metadata import version
|
||||||
|
try:
|
||||||
|
from importlib.metadata import (distribution, metadata,
|
||||||
|
version)
|
||||||
|
meta = metadata(key)
|
||||||
|
result.append({row: meta[row]
|
||||||
|
for row in list(metadata(key))})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("FAILED to load plugin {} ".fromat(key))
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
for p in data["plugins"]:
|
|
||||||
try:
|
|
||||||
p_metadata= metadata(p)
|
|
||||||
p_name = p_metadata['name']
|
|
||||||
p_version = p_metadata['Version']
|
|
||||||
p_summary = p_metadata['Summary']
|
|
||||||
p_homepage= p_metadata['Home-page']
|
|
||||||
p_author = p_metadata['Author']
|
|
||||||
p_author_email = p_metadata['Author-email']
|
|
||||||
p_license = p_metadata['License']
|
|
||||||
p_description = p_metadata['Description']
|
|
||||||
plugin_data = {'Name': p_name,'Version': p_version,'Summary': p_summary,'Homepage':p_homepage,'Author':p_author,'Email': p_author_email,'License': p_license,'Description': p_description}
|
|
||||||
result.append(plugin_data)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# print("- ({})\t{}".format(p_version,p))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
logger.error(e)
|
||||||
return []
|
return []
|
||||||
pass
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -20,7 +20,7 @@ class StepController:
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.path = os.path.join(".", 'config', "step_data.json")
|
self.path = os.path.join(".", 'config', "step_data.json")
|
||||||
self._loop = asyncio.get_event_loop()
|
#self._loop = asyncio.get_event_loop()
|
||||||
self.basic_data = {}
|
self.basic_data = {}
|
||||||
self.step = None
|
self.step = None
|
||||||
self.types = {}
|
self.types = {}
|
||||||
|
@ -69,7 +69,8 @@ class StepController:
|
||||||
if startActive is True:
|
if startActive is True:
|
||||||
active_step = self.find_by_status("A")
|
active_step = self.find_by_status("A")
|
||||||
if active_step is not None:
|
if active_step is not None:
|
||||||
self._loop.create_task(self.start_step(active_step))
|
asyncio.get_event_loop().create_task(self.start_step(active_step))
|
||||||
|
#self._loop.create_task(self.start_step(active_step))
|
||||||
|
|
||||||
async def add(self, item: Step):
|
async def add(self, item: Step):
|
||||||
logging.debug("Add step")
|
logging.debug("Add step")
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
from asyncio import set_event_loop_policy, WindowsSelectorEventLoopPolicy
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
import json
|
import json
|
||||||
from voluptuous.schema_builder import message
|
from voluptuous.schema_builder import message
|
||||||
from cbpi.api.dataclasses import NotificationType
|
from cbpi.api.dataclasses import NotificationType
|
||||||
|
@ -82,6 +87,11 @@ async def error_middleware(request, handler):
|
||||||
class CraftBeerPi:
|
class CraftBeerPi:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
operationsystem= sys.platform
|
||||||
|
if operationsystem.startswith('win'):
|
||||||
|
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
|
||||||
|
|
||||||
self.path = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-1]) # The path to the package dir
|
self.path = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-1]) # The path to the package dir
|
||||||
|
|
||||||
self.version = __version__
|
self.version = __version__
|
||||||
|
|
|
@ -32,8 +32,9 @@ class FermenterNotificationStep(CBPiFermentationStep):
|
||||||
|
|
||||||
if self.AutoNext == True:
|
if self.AutoNext == True:
|
||||||
self.cbpi.notify(self.name, self.props.get("Notification",""), NotificationType.INFO)
|
self.cbpi.notify(self.name, self.props.get("Notification",""), NotificationType.INFO)
|
||||||
await self.next(self.fermenter.id)
|
if self.shutdown != True:
|
||||||
return StepResult.DONE
|
await self.next(self.fermenter.id)
|
||||||
|
return StepResult.DONE
|
||||||
else:
|
else:
|
||||||
self.cbpi.notify(self.name, self.props.get("Notification",""), NotificationType.INFO, action=[NotificationAction("Next Step", self.NextStep)])
|
self.cbpi.notify(self.name, self.props.get("Notification",""), NotificationType.INFO, action=[NotificationAction("Next Step", self.NextStep)])
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
@ -42,6 +43,7 @@ class FermenterNotificationStep(CBPiFermentationStep):
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
|
||||||
async def on_start(self):
|
async def on_start(self):
|
||||||
|
self.shutdown = False
|
||||||
self.summary=""
|
self.summary=""
|
||||||
self.AutoNext = False if self.props.get("AutoNext", "No") == "No" else True
|
self.AutoNext = False if self.props.get("AutoNext", "No") == "No" else True
|
||||||
if self.timer is None:
|
if self.timer is None:
|
||||||
|
@ -69,13 +71,12 @@ class FermenterNotificationStep(CBPiFermentationStep):
|
||||||
class FermenterTargetTempStep(CBPiFermentationStep):
|
class FermenterTargetTempStep(CBPiFermentationStep):
|
||||||
|
|
||||||
async def NextStep(self, **kwargs):
|
async def NextStep(self, **kwargs):
|
||||||
await self.next(self.fermenter.id)
|
if self.shutdown != True:
|
||||||
return StepResult.DONE
|
await self.next(self.fermenter.id)
|
||||||
|
return StepResult.DONE
|
||||||
|
|
||||||
async def on_timer_done(self,timer):
|
async def on_timer_done(self,timer):
|
||||||
self.summary = ""
|
self.summary = ""
|
||||||
self.fermenter.target_temp = 0
|
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
if self.AutoMode == True:
|
if self.AutoMode == True:
|
||||||
await self.setAutoMode(False)
|
await self.setAutoMode(False)
|
||||||
|
@ -88,8 +89,8 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
|
||||||
async def on_start(self):
|
async def on_start(self):
|
||||||
|
self.shutdown = False
|
||||||
self.AutoMode = True if self.props.get("AutoMode","No") == "Yes" else False
|
self.AutoMode = True if self.props.get("AutoMode","No") == "Yes" else False
|
||||||
self.starttemp= self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
|
||||||
if self.fermenter is not None:
|
if self.fermenter is not None:
|
||||||
self.fermenter.target_temp = int(self.props.get("Temp", 0))
|
self.fermenter.target_temp = int(self.props.get("Temp", 0))
|
||||||
if self.AutoMode == True:
|
if self.AutoMode == True:
|
||||||
|
@ -107,6 +108,9 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
while self.get_sensor_value(self.props.get("Sensor", None)).get("value") > 900:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
self.starttemp= self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
||||||
if self.fermenter.target_temp >= self.starttemp:
|
if self.fermenter.target_temp >= self.starttemp:
|
||||||
logging.info("warmup")
|
logging.info("warmup")
|
||||||
while self.running == True:
|
while self.running == True:
|
||||||
|
@ -128,6 +132,7 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
||||||
|
|
||||||
async def reset(self):
|
async def reset(self):
|
||||||
self.timer = Timer(1 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
self.timer = Timer(1 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||||
|
self.timer.is_running == False
|
||||||
|
|
||||||
async def setAutoMode(self, auto_state):
|
async def setAutoMode(self, auto_state):
|
||||||
try:
|
try:
|
||||||
|
@ -155,28 +160,32 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
self.cbpi.notify(self.name, 'Timer started', NotificationType.INFO)
|
self.cbpi.notify(self.name, 'Timer started', NotificationType.INFO)
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
self.timer.is_running = True
|
self.timer.is_running = True
|
||||||
|
self.endtime = time.time() + self.fermentationtime
|
||||||
|
await self.update_endtime()
|
||||||
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
||||||
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
||||||
else:
|
else:
|
||||||
self.cbpi.notify(self.name, 'Timer is already running', NotificationType.WARNING)
|
self.cbpi.notify(self.name, 'Timer is already running', NotificationType.WARNING)
|
||||||
|
|
||||||
@action("Add 5 Minutes to Timer", [])
|
# @action("Add 1 Day to Timer", [])
|
||||||
async def add_timer(self):
|
# async def add_timer(self):
|
||||||
if self.timer.is_running == True:
|
# if self.timer.is_running == True:
|
||||||
self.cbpi.notify(self.name, '5 Minutes added', NotificationType.INFO)
|
# self.cbpi.notify(self.name, '1 Day added', NotificationType.INFO)
|
||||||
await self.timer.add(300)
|
# await self.timer.add(86400)
|
||||||
else:
|
# self.endtime = self.endtime +86400
|
||||||
self.cbpi.notify(self.name, 'Timer must be running to add time', NotificationType.WARNING)
|
# await self.update_endtime()
|
||||||
|
# else:
|
||||||
|
# self.cbpi.notify(self.name, 'Timer must be running to add time', NotificationType.WARNING)
|
||||||
|
|
||||||
|
|
||||||
async def on_timer_done(self,timer):
|
async def on_timer_done(self,timer):
|
||||||
self.summary = ""
|
self.summary = ""
|
||||||
self.fermenter.target_temp = 0
|
|
||||||
if self.AutoMode == True:
|
if self.AutoMode == True:
|
||||||
await self.setAutoMode(False)
|
await self.setAutoMode(False)
|
||||||
self.cbpi.notify(self.name, 'Step finished', NotificationType.SUCCESS)
|
self.cbpi.notify(self.name, 'Step finished', NotificationType.SUCCESS)
|
||||||
await self.next(self.fermenter.id)
|
if self.shutdown != True:
|
||||||
return StepResult.DONE
|
await self.next(self.fermenter.id)
|
||||||
|
return StepResult.DONE
|
||||||
|
|
||||||
|
|
||||||
async def on_timer_update(self,timer, seconds):
|
async def on_timer_update(self,timer, seconds):
|
||||||
|
@ -184,13 +193,16 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
|
||||||
async def on_start(self):
|
async def on_start(self):
|
||||||
timeD=int(self.props.get("TimerD", 0))
|
self.shutdown = False
|
||||||
timeH=int(self.props.get("TimerH", 0))
|
if self.endtime == 0:
|
||||||
timeM=int(self.props.get("TimerM", 0))
|
timeD=int(self.props.get("TimerD", 0))
|
||||||
self.fermentationtime=(timeM+(60*timeH)+(1440*timeD)) *60
|
timeH=int(self.props.get("TimerH", 0))
|
||||||
|
timeM=int(self.props.get("TimerM", 0))
|
||||||
|
self.fermentationtime=(timeM+(60*timeH)+(1440*timeD)) *60
|
||||||
|
else:
|
||||||
|
self.fermentationtime = self.endtime - time.time()
|
||||||
|
|
||||||
self.AutoMode = True if self.props.get("AutoMode", "No") == "Yes" else False
|
self.AutoMode = True if self.props.get("AutoMode", "No") == "Yes" else False
|
||||||
self.starttemp= self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
|
||||||
if self.fermenter is not None:
|
if self.fermenter is not None:
|
||||||
self.fermenter.target_temp = int(self.props.get("Temp", 0))
|
self.fermenter.target_temp = int(self.props.get("Temp", 0))
|
||||||
if self.AutoMode == True:
|
if self.AutoMode == True:
|
||||||
|
@ -199,13 +211,23 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
|
|
||||||
if self.fermenter is not None and self.timer is None:
|
if self.fermenter is not None and self.timer is None:
|
||||||
self.timer = Timer(self.fermentationtime ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
self.timer = Timer(self.fermentationtime ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||||
|
self.timer.is_running = False
|
||||||
elif self.fermenter is not None:
|
elif self.fermenter is not None:
|
||||||
try:
|
try:
|
||||||
if self.timer.is_running == True:
|
if self.timer.is_running == True:
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
self.endtime = time.time() + self.fermentationtime
|
||||||
|
await self.update_endtime()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if self.endtime != 0 and self.timer is not None and self.timer.is_running == False:
|
||||||
|
self.timer.start()
|
||||||
|
self.timer.is_running = True
|
||||||
|
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
||||||
|
self.cbpi.notify(self.name, 'Timer restarted. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
||||||
|
|
||||||
|
|
||||||
self.summary = "Waiting for Target Temp"
|
self.summary = "Waiting for Target Temp"
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
|
||||||
|
@ -217,9 +239,19 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
|
||||||
async def reset(self):
|
async def reset(self):
|
||||||
|
timeD=int(self.props.get("TimerD", 0))
|
||||||
|
timeH=int(self.props.get("TimerH", 0))
|
||||||
|
timeM=int(self.props.get("TimerM", 0))
|
||||||
|
self.fermentationtime=(timeM+(60*timeH)+(1440*timeD)) *60
|
||||||
self.timer = Timer(self.fermentationtime ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
self.timer = Timer(self.fermentationtime ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||||
|
self.endtime = 0
|
||||||
|
self.timer.is_running == False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
while self.get_sensor_value(self.props.get("Sensor", None)).get("value") > 900:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
self.starttemp= self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
||||||
|
|
||||||
if self.fermenter.target_temp >= self.starttemp:
|
if self.fermenter.target_temp >= self.starttemp:
|
||||||
logging.info("warmup")
|
logging.info("warmup")
|
||||||
while self.running == True:
|
while self.running == True:
|
||||||
|
@ -228,6 +260,8 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
if sensor_value >= self.fermenter.target_temp and self.timer.is_running is not True:
|
if sensor_value >= self.fermenter.target_temp and self.timer.is_running is not True:
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
self.timer.is_running = True
|
self.timer.is_running = True
|
||||||
|
self.endtime = time.time() + self.fermentationtime
|
||||||
|
await self.update_endtime()
|
||||||
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
||||||
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
||||||
elif self.fermenter.target_temp <= self.starttemp:
|
elif self.fermenter.target_temp <= self.starttemp:
|
||||||
|
@ -238,6 +272,8 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
if sensor_value <= self.fermenter.target_temp and self.timer.is_running is not True:
|
if sensor_value <= self.fermenter.target_temp and self.timer.is_running is not True:
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
self.timer.is_running = True
|
self.timer.is_running = True
|
||||||
|
self.endtime = time.time() + self.fermentationtime
|
||||||
|
await self.update_endtime()
|
||||||
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
estimated_completion_time = datetime.fromtimestamp(time.time()+ self.fermentationtime)
|
||||||
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%d.%m, %H:%M")), NotificationType.INFO)
|
||||||
|
|
||||||
|
@ -254,43 +290,6 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
||||||
|
|
||||||
@parameters([Property.Number(label="TimerD", description="Timer Days", configurable=True),
|
|
||||||
Property.Number(label="TimerH", description="Timer Hours", configurable=True),
|
|
||||||
Property.Number(label="TimerM", description="Timer Minutes", configurable=True)
|
|
||||||
])
|
|
||||||
class FermenterWaitStep(CBPiFermentationStep):
|
|
||||||
|
|
||||||
async def on_timer_done(self, timer):
|
|
||||||
self.summary = ""
|
|
||||||
await self.next(self.fermenter.id)
|
|
||||||
return StepResult.DONE
|
|
||||||
|
|
||||||
async def on_timer_update(self, timer, seconds):
|
|
||||||
self.summary = Timer.format_time(seconds)
|
|
||||||
await self.push_update()
|
|
||||||
|
|
||||||
async def on_start(self):
|
|
||||||
timeD=int(self.props.get("TimerD", 0))
|
|
||||||
timeH=int(self.props.get("TimerH", 0))
|
|
||||||
timeM=int(self.props.get("TimerM", 0))
|
|
||||||
self.fermentationtime=(timeM+(60*timeH)+(1440*timeD)) *60
|
|
||||||
|
|
||||||
if self.timer is None:
|
|
||||||
self.timer = Timer(self.fermentationtime, on_update=self.on_timer_update, on_done=self.on_timer_done)
|
|
||||||
self.timer.start()
|
|
||||||
|
|
||||||
async def on_stop(self):
|
|
||||||
await self.timer.stop()
|
|
||||||
self.summary = ""
|
|
||||||
await self.push_update()
|
|
||||||
|
|
||||||
async def reset(self):
|
|
||||||
self.timer = Timer(self.fermentationtime, on_update=self.on_timer_update, on_done=self.on_timer_done)
|
|
||||||
|
|
||||||
async def run(self):
|
|
||||||
while self.running == True:
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
return StepResult.DONE
|
|
||||||
|
|
||||||
def setup(cbpi):
|
def setup(cbpi):
|
||||||
'''
|
'''
|
||||||
|
@ -304,4 +303,3 @@ def setup(cbpi):
|
||||||
cbpi.plugin.register("FermenterNotificationStep", FermenterNotificationStep)
|
cbpi.plugin.register("FermenterNotificationStep", FermenterNotificationStep)
|
||||||
cbpi.plugin.register("FermenterTargetTempStep", FermenterTargetTempStep)
|
cbpi.plugin.register("FermenterTargetTempStep", FermenterTargetTempStep)
|
||||||
cbpi.plugin.register("FermenterStep", FermenterStep)
|
cbpi.plugin.register("FermenterStep", FermenterStep)
|
||||||
cbpi.plugin.register("FermenterWaitStep", FermenterWaitStep)
|
|
|
@ -32,7 +32,7 @@ class FermenterAutostart(CBPiExtension):
|
||||||
self.fermenter=self.cbpi.fermenter._find_by_id(fermenter_id)
|
self.fermenter=self.cbpi.fermenter._find_by_id(fermenter_id)
|
||||||
try:
|
try:
|
||||||
if (self.fermenter.instance is None or self.fermenter.instance.state == False):
|
if (self.fermenter.instance is None or self.fermenter.instance.state == False):
|
||||||
await self.cbpi.fermenter.toggle(self.fermenter.id)
|
await self.cbpi.fermenter.start(self.fermenter.id)
|
||||||
logging.info("Successfully switched on Ferenterlogic for Fermenter {}".format(self.fermenter.id))
|
logging.info("Successfully switched on Ferenterlogic for Fermenter {}".format(self.fermenter.id))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
||||||
|
|
|
@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
|
||||||
try:
|
try:
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Failed to load RPi.GPIO. Using Mock")
|
logger.warning("Failed to load RPi.GPIO. Using Mock instead")
|
||||||
MockRPi = MagicMock()
|
MockRPi = MagicMock()
|
||||||
modules = {
|
modules = {
|
||||||
"RPi": MockRPi,
|
"RPi": MockRPi,
|
||||||
|
|
|
@ -18,7 +18,7 @@ class MQTTSensor(CBPiSensor):
|
||||||
if self.payload_text != None:
|
if self.payload_text != None:
|
||||||
self.payload_text = self.payload_text.split('.')
|
self.payload_text = self.payload_text.split('.')
|
||||||
self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message)
|
self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message)
|
||||||
self.value: int = 0
|
self.value: float = 999
|
||||||
|
|
||||||
async def on_message(self, message):
|
async def on_message(self, message):
|
||||||
val = json.loads(message)
|
val = json.loads(message)
|
||||||
|
|
|
@ -313,8 +313,8 @@ class FermentationHttpEndpoints():
|
||||||
|
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
fermenterid= request.match_info['id']
|
fermenterid= request.match_info['id']
|
||||||
newstep = {"name": data.get("name"), "props": data.get("props", {}), "type": data.get("type")}
|
newstep = {"name": data.get("name"), "props": data.get("props", {}), "endtime": 0, "type": data.get("type")}
|
||||||
response_data = await self.controller.create_step(fermenterid,newstep)
|
response_data = await self.controller.add_step(fermenterid,newstep)
|
||||||
return web.json_response(data=response_data.to_dict())
|
return web.json_response(data=response_data.to_dict())
|
||||||
|
|
||||||
@request_mapping(path="/{fermenterid}/{stepid}", method="PUT", auth_required=False)
|
@request_mapping(path="/{fermenterid}/{stepid}", method="PUT", auth_required=False)
|
||||||
|
@ -352,7 +352,7 @@ class FermentationHttpEndpoints():
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
stepid = request.match_info['stepid']
|
stepid = request.match_info['stepid']
|
||||||
fermenterid = request.match_info['fermenterid']
|
fermenterid = request.match_info['fermenterid']
|
||||||
updatedstep = {"id": stepid, "name": data.get("name"), "props": data.get("props", {}), "type": data.get("type")}
|
updatedstep = {"id": stepid, "name": data.get("name"), "endtime": 0, "props": data.get("props", {}), "type": data.get("type")}
|
||||||
#step = FermenterStep(stepid, data.get("name"), None, Props(data.get("props", {})), data.get("type"))
|
#step = FermenterStep(stepid, data.get("name"), None, Props(data.get("props", {})), data.get("type"))
|
||||||
await self.controller.update_step(fermenterid,updatedstep)
|
await self.controller.update_step(fermenterid,updatedstep)
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
|
@ -117,7 +117,7 @@ class FermenterRecipeHttpEndpoints():
|
||||||
await self.controller.remove(name)
|
await self.controller.remove(name)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
@request_mapping(path="/{name}/{fermenterid}/brew", method="POST", auth_required=False)
|
@request_mapping(path="/{recipeid}/{fermenterid}/{name}/brew", method="POST", auth_required=False)
|
||||||
async def http_brew(self, request):
|
async def http_brew(self, request):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -137,9 +137,10 @@ class FermenterRecipeHttpEndpoints():
|
||||||
"200":
|
"200":
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
|
recipeid = request.match_info['recipeid']
|
||||||
name = request.match_info['name']
|
name = request.match_info['name']
|
||||||
fermenterid = request.match_info['fermenterid']
|
fermenterid = request.match_info['fermenterid']
|
||||||
await self.controller.brew(name,fermenterid)
|
await self.controller.brew(recipeid,fermenterid,name)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
@request_mapping(path="/{id}/clone", method="POST", auth_required=False)
|
@request_mapping(path="/{id}/clone", method="POST", auth_required=False)
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
aiohttp==3.7.4
|
aiohttp==3.8.1
|
||||||
aiohttp-auth==0.1.1
|
aiohttp-auth==0.1.1
|
||||||
aiohttp-route-decorator==0.1.4
|
aiohttp-route-decorator==0.1.4
|
||||||
aiohttp-security==0.4.0
|
aiohttp-security==0.4.0
|
||||||
aiohttp-session==2.9.0
|
aiohttp-session==2.11.0
|
||||||
aiohttp-swagger==1.0.15
|
aiohttp-swagger==1.0.16
|
||||||
aiojobs==0.3.0
|
aiojobs==1.0.0
|
||||||
aiosqlite==0.16.0
|
aiosqlite==0.17.0
|
||||||
cryptography==3.3.2
|
cryptography==36.0.1
|
||||||
requests==2.25.1
|
requests==2.27.1
|
||||||
voluptuous==0.12.1
|
voluptuous==0.12.2
|
||||||
pyfiglet==0.8.post1
|
pyfiglet==0.8.post1
|
||||||
pandas==1.4.0
|
pandas==1.4.1
|
||||||
shortuuid==1.0.1
|
shortuuid==1.0.8
|
||||||
tabulate==0.8.7
|
tabulate==0.8.9
|
||||||
numpy==1.22.0
|
numpy==1.22.2
|
||||||
cbpi4ui
|
cbpi4ui
|
||||||
click==7.1.2
|
click==8.0.4
|
||||||
importlib_metadata==4.8.2
|
importlib_metadata==4.11.1
|
||||||
asyncio-mqtt
|
asyncio-mqtt
|
||||||
psutil==5.8.0
|
psutil==5.9.0
|
||||||
zipp>=0.5
|
zipp>=0.5
|
||||||
|
PyInquirer==1.0.3
|
||||||
|
colorama==0.4.4
|
37
setup.py
37
setup.py
|
@ -27,32 +27,33 @@ setup(name='cbpi',
|
||||||
'': ['*.txt', '*.rst', '*.yaml'],
|
'': ['*.txt', '*.rst', '*.yaml'],
|
||||||
'cbpi': ['*','*.txt', '*.rst', '*.yaml']},
|
'cbpi': ['*','*.txt', '*.rst', '*.yaml']},
|
||||||
|
|
||||||
python_requires='>=3',
|
python_requires='>=3.9',
|
||||||
|
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"aiohttp==3.7.4",
|
"aiohttp==3.8.1",
|
||||||
"aiohttp-auth==0.1.1",
|
"aiohttp-auth==0.1.1",
|
||||||
"aiohttp-route-decorator==0.1.4",
|
"aiohttp-route-decorator==0.1.4",
|
||||||
"aiohttp-security==0.4.0",
|
"aiohttp-security==0.4.0",
|
||||||
"aiohttp-session==2.9.0",
|
"aiohttp-session==2.11.0",
|
||||||
"aiohttp-swagger==1.0.15",
|
"aiohttp-swagger==1.0.16",
|
||||||
"aiojobs==0.3.0",
|
"aiojobs==1.0.0 ",
|
||||||
"aiosqlite==0.16.0",
|
"aiosqlite==0.17.0",
|
||||||
"cryptography==3.3.2",
|
"cryptography==36.0.1",
|
||||||
"requests==2.25.1",
|
"requests==2.27.1",
|
||||||
"voluptuous==0.12.1",
|
"voluptuous==0.12.2",
|
||||||
"pyfiglet==0.8.post1",
|
"pyfiglet==0.8.post1",
|
||||||
'click==7.1.2',
|
'click==8.0.4',
|
||||||
'shortuuid==1.0.1',
|
'shortuuid==1.0.8',
|
||||||
'tabulate==0.8.7',
|
'tabulate==0.8.9',
|
||||||
'asyncio-mqtt',
|
'asyncio-mqtt',
|
||||||
'psutil==5.8.0',
|
'PyInquirer==1.0.3',
|
||||||
|
'colorama==0.4.4',
|
||||||
|
'psutil==5.9.0',
|
||||||
'cbpi4ui',
|
'cbpi4ui',
|
||||||
'importlib_metadata'] + (
|
'importlib_metadata',
|
||||||
['RPi.GPIO==0.7.1'] if raspberrypi else [] ) +
|
'numpy==1.22.2',
|
||||||
(['numpy==1.22.0'] if (int(platform.python_version_tuple()[1]) >= 9) and (int(platform.python_version_tuple()[0]) == 3) else ['numpy==1.20.3'] ) +
|
'pandas==1.4.1'] + (
|
||||||
(['pandas==1.4.0'] if (int(platform.python_version_tuple()[1]) >= 9) and (int(platform.python_version_tuple()[0]) == 3) else ['pandas==1.1.5'] ),
|
['RPi.GPIO==0.7.1'] if raspberrypi else [] ),
|
||||||
|
|
||||||
|
|
||||||
dependency_links=[
|
dependency_links=[
|
||||||
'https://testpypi.python.org/pypi',
|
'https://testpypi.python.org/pypi',
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
# This file must be used with "source bin/activate" *from bash*
|
|
||||||
# you cannot run it directly
|
|
||||||
|
|
||||||
deactivate () {
|
|
||||||
# reset old environment variables
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
|
||||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
|
||||||
export PATH
|
|
||||||
unset _OLD_VIRTUAL_PATH
|
|
||||||
fi
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
|
||||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
|
||||||
export PYTHONHOME
|
|
||||||
unset _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
|
||||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
|
||||||
export PS1
|
|
||||||
unset _OLD_VIRTUAL_PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset VIRTUAL_ENV
|
|
||||||
if [ ! "$1" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV="/Users/manuelfritsch/Documents/git/cbpi4/venv3"
|
|
||||||
export VIRTUAL_ENV
|
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
export PATH
|
|
||||||
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
|
||||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
|
||||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
|
||||||
unset PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
|
||||||
if [ "x(venv3) " != x ] ; then
|
|
||||||
PS1="(venv3) ${PS1:-}"
|
|
||||||
else
|
|
||||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
|
||||||
# special case for Aspen magic directories
|
|
||||||
# see http://www.zetadev.com/software/aspen/
|
|
||||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
|
||||||
else
|
|
||||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
export PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r
|
|
||||||
fi
|
|
|
@ -1,37 +0,0 @@
|
||||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
|
||||||
# You cannot run it directly.
|
|
||||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
|
||||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
|
||||||
|
|
||||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
setenv VIRTUAL_ENV "/Users/manuelfritsch/Documents/git/cbpi4/venv3"
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
|
||||||
|
|
||||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
|
||||||
if ("venv3" != "") then
|
|
||||||
set env_name = "venv3"
|
|
||||||
else
|
|
||||||
if (`basename "VIRTUAL_ENV"` == "__") then
|
|
||||||
# special case for Aspen magic directories
|
|
||||||
# see http://www.zetadev.com/software/aspen/
|
|
||||||
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
|
||||||
else
|
|
||||||
set env_name = `basename "$VIRTUAL_ENV"`
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
set prompt = "[$env_name] $prompt"
|
|
||||||
unset env_name
|
|
||||||
endif
|
|
||||||
|
|
||||||
alias pydoc python -m pydoc
|
|
||||||
|
|
||||||
rehash
|
|
|
@ -1,75 +0,0 @@
|
||||||
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
|
|
||||||
# you cannot run it directly
|
|
||||||
|
|
||||||
function deactivate -d "Exit virtualenv and return to normal shell environment"
|
|
||||||
# reset old environment variables
|
|
||||||
if test -n "$_OLD_VIRTUAL_PATH"
|
|
||||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
|
||||||
set -e _OLD_VIRTUAL_PATH
|
|
||||||
end
|
|
||||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
|
||||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
|
||||||
functions -e fish_prompt
|
|
||||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
|
||||||
functions -c _old_fish_prompt fish_prompt
|
|
||||||
functions -e _old_fish_prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
set -e VIRTUAL_ENV
|
|
||||||
if test "$argv[1]" != "nondestructive"
|
|
||||||
# Self destruct!
|
|
||||||
functions -e deactivate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV "/Users/manuelfritsch/Documents/git/cbpi4/venv3"
|
|
||||||
|
|
||||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
|
||||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
|
||||||
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
if set -q PYTHONHOME
|
|
||||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
|
||||||
set -e PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
|
||||||
# fish uses a function instead of an env var to generate the prompt.
|
|
||||||
|
|
||||||
# save the current fish_prompt function as the function _old_fish_prompt
|
|
||||||
functions -c fish_prompt _old_fish_prompt
|
|
||||||
|
|
||||||
# with the original prompt function renamed, we can override with our own.
|
|
||||||
function fish_prompt
|
|
||||||
# Save the return status of the last command
|
|
||||||
set -l old_status $status
|
|
||||||
|
|
||||||
# Prompt override?
|
|
||||||
if test -n "(venv3) "
|
|
||||||
printf "%s%s" "(venv3) " (set_color normal)
|
|
||||||
else
|
|
||||||
# ...Otherwise, prepend env
|
|
||||||
set -l _checkbase (basename "$VIRTUAL_ENV")
|
|
||||||
if test $_checkbase = "__"
|
|
||||||
# special case for Aspen magic directories
|
|
||||||
# see http://www.zetadev.com/software/aspen/
|
|
||||||
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
|
||||||
else
|
|
||||||
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Restore the return status of the previous command.
|
|
||||||
echo "exit $old_status" | .
|
|
||||||
_old_fish_prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
|
||||||
end
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from autopep8 import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,12 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
# EASY-INSTALL-ENTRY-SCRIPT: 'cbpi==4.0.0.28','console_scripts','cbpi'
|
|
||||||
__requires__ = 'cbpi==4.0.0.28'
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pkg_resources import load_entry_point
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(
|
|
||||||
load_entry_point('cbpi==4.0.0.28', 'console_scripts', 'cbpi')()
|
|
||||||
)
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from chardet.cli.chardetect import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from numpy.f2py.f2py2e import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from numpy.f2py.f2py2e import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from numpy.f2py.f2py2e import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pip._internal import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pip._internal import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pip._internal import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pycodestyle import _main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(_main())
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pyfiglet import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1 +0,0 @@
|
||||||
python3
|
|
|
@ -1 +0,0 @@
|
||||||
/Library/Frameworks/Python.framework/Versions/3.7/bin/python3
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/Users/manuelfritsch/Documents/git/cbpi4/venv3/bin/python3
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from tabulate import _main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(_main())
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,28 +0,0 @@
|
||||||
Copyright 2007 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,106 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: Jinja2
|
|
||||||
Version: 2.11.3
|
|
||||||
Summary: A very fast and expressive template engine.
|
|
||||||
Home-page: https://palletsprojects.com/p/jinja/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: Pallets
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Documentation, https://jinja.palletsprojects.com/
|
|
||||||
Project-URL: Code, https://github.com/pallets/jinja
|
|
||||||
Project-URL: Issue tracker, https://github.com/pallets/jinja/issues
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
||||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
Requires-Dist: MarkupSafe (>=0.23)
|
|
||||||
Provides-Extra: i18n
|
|
||||||
Requires-Dist: Babel (>=0.8) ; extra == 'i18n'
|
|
||||||
|
|
||||||
Jinja
|
|
||||||
=====
|
|
||||||
|
|
||||||
Jinja is a fast, expressive, extensible templating engine. Special
|
|
||||||
placeholders in the template allow writing code similar to Python
|
|
||||||
syntax. Then the template is passed data to render the final document.
|
|
||||||
|
|
||||||
It includes:
|
|
||||||
|
|
||||||
- Template inheritance and inclusion.
|
|
||||||
- Define and import macros within templates.
|
|
||||||
- HTML templates can use autoescaping to prevent XSS from untrusted
|
|
||||||
user input.
|
|
||||||
- A sandboxed environment can safely render untrusted templates.
|
|
||||||
- AsyncIO support for generating templates and calling async
|
|
||||||
functions.
|
|
||||||
- I18N support with Babel.
|
|
||||||
- Templates are compiled to optimized Python code just-in-time and
|
|
||||||
cached, or can be compiled ahead-of-time.
|
|
||||||
- Exceptions point to the correct line in templates to make debugging
|
|
||||||
easier.
|
|
||||||
- Extensible filters, tests, functions, and even syntax.
|
|
||||||
|
|
||||||
Jinja's philosophy is that while application logic belongs in Python if
|
|
||||||
possible, it shouldn't make the template designer's job difficult by
|
|
||||||
restricting functionality too much.
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ pip install -U Jinja2
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
In A Nutshell
|
|
||||||
-------------
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Members{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Website: https://palletsprojects.com/p/jinja/
|
|
||||||
- Documentation: https://jinja.palletsprojects.com/
|
|
||||||
- Releases: https://pypi.org/project/Jinja2/
|
|
||||||
- Code: https://github.com/pallets/jinja
|
|
||||||
- Issue tracker: https://github.com/pallets/jinja/issues
|
|
||||||
- Test status: https://dev.azure.com/pallets/jinja/_build
|
|
||||||
- Official chat: https://discord.gg/t6rrQZH
|
|
||||||
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
jinja2/__init__.py,sha256=LZUXmxJc2GIchfSAeMWsxCWiQYO-w1-736f2Q3I8ms8,1549
|
|
||||||
jinja2/_compat.py,sha256=B6Se8HjnXVpzz9-vfHejn-DV2NjaVK-Iewupc5kKlu8,3191
|
|
||||||
jinja2/_identifier.py,sha256=EdgGJKi7O1yvr4yFlvqPNEqV6M1qHyQr8Gt8GmVTKVM,1775
|
|
||||||
jinja2/asyncfilters.py,sha256=XJtYXTxFvcJ5xwk6SaDL4S0oNnT0wPYvXBCSzc482fI,4250
|
|
||||||
jinja2/asyncsupport.py,sha256=ZBFsDLuq3Gtji3Ia87lcyuDbqaHZJRdtShZcqwpFnSQ,7209
|
|
||||||
jinja2/bccache.py,sha256=3Pmp4jo65M9FQuIxdxoDBbEDFwe4acDMQf77nEJfrHA,12139
|
|
||||||
jinja2/compiler.py,sha256=Ta9W1Lit542wItAHXlDcg0sEOsFDMirCdlFPHAurg4o,66284
|
|
||||||
jinja2/constants.py,sha256=RR1sTzNzUmKco6aZicw4JpQpJGCuPuqm1h1YmCNUEFY,1458
|
|
||||||
jinja2/debug.py,sha256=neR7GIGGjZH3_ILJGVUYy3eLQCCaWJMXOb7o0kGInWc,8529
|
|
||||||
jinja2/defaults.py,sha256=85B6YUUCyWPSdrSeVhcqFVuu_bHUAQXeey--FIwSeVQ,1126
|
|
||||||
jinja2/environment.py,sha256=XDSLKc4SqNLMOwTSq3TbWEyA5WyXfuLuVD0wAVjEFwM,50629
|
|
||||||
jinja2/exceptions.py,sha256=VjNLawcmf2ODffqVMCQK1cRmvFaUfQWF4u8ouP3QPcE,5425
|
|
||||||
jinja2/ext.py,sha256=AtwL5O5enT_L3HR9-oBvhGyUTdGoyaqG_ICtnR_EVd4,26441
|
|
||||||
jinja2/filters.py,sha256=9ORilsZrUoydSI9upz8_qGy7gozDWLYoFmlIBFSVRnQ,41439
|
|
||||||
jinja2/idtracking.py,sha256=J3O4VHsrbf3wzwiBc7Cro26kHb6_5kbULeIOzocchIU,9211
|
|
||||||
jinja2/lexer.py,sha256=nUFLRKhhKmmEWkLI65nQePgcQs7qsRdjVYZETMt_v0g,30331
|
|
||||||
jinja2/loaders.py,sha256=C-fST_dmFjgWkp0ZuCkrgICAoOsoSIF28wfAFink0oU,17666
|
|
||||||
jinja2/meta.py,sha256=QjyYhfNRD3QCXjBJpiPl9KgkEkGXJbAkCUq4-Ur10EQ,4131
|
|
||||||
jinja2/nativetypes.py,sha256=Ul__gtVw4xH-0qvUvnCNHedQeNDwmEuyLJztzzSPeRg,2753
|
|
||||||
jinja2/nodes.py,sha256=Mk1oJPVgIjnQw9WOqILvcu3rLepcFZ0ahxQm2mbwDwc,31095
|
|
||||||
jinja2/optimizer.py,sha256=gQLlMYzvQhluhzmAIFA1tXS0cwgWYOjprN-gTRcHVsc,1457
|
|
||||||
jinja2/parser.py,sha256=fcfdqePNTNyvosIvczbytVA332qpsURvYnCGcjDHSkA,35660
|
|
||||||
jinja2/runtime.py,sha256=0y-BRyIEZ9ltByL2Id6GpHe1oDRQAwNeQvI0SKobNMw,30618
|
|
||||||
jinja2/sandbox.py,sha256=knayyUvXsZ-F0mk15mO2-ehK9gsw04UhB8td-iUOtLc,17127
|
|
||||||
jinja2/tests.py,sha256=iO_Y-9Vo60zrVe1lMpSl5sKHqAxe2leZHC08OoZ8K24,4799
|
|
||||||
jinja2/utils.py,sha256=Wy4yC3IByqUWwnKln6SdaixdzgK74P6F5nf-gQZrYnU,22436
|
|
||||||
jinja2/visitor.py,sha256=DUHupl0a4PGp7nxRtZFttUzAi1ccxzqc2hzetPYUz8U,3240
|
|
||||||
Jinja2-2.11.3.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
|
|
||||||
Jinja2-2.11.3.dist-info/METADATA,sha256=PscpJ1C3RSp8xcjV3fAuTz13rKbGxmzJXnMQFH-WKhs,3535
|
|
||||||
Jinja2-2.11.3.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
|
|
||||||
Jinja2-2.11.3.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61
|
|
||||||
Jinja2-2.11.3.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
|
|
||||||
Jinja2-2.11.3.dist-info/RECORD,,
|
|
||||||
Jinja2-2.11.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
jinja2/__pycache__/lexer.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/parser.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/constants.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/idtracking.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/debug.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/sandbox.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/exceptions.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/ext.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/loaders.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/environment.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/meta.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/asyncfilters.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/asyncsupport.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/tests.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/optimizer.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/_identifier.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/bccache.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/compiler.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/_compat.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/defaults.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/nodes.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/utils.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/filters.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/runtime.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/__init__.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/visitor.cpython-37.pyc,,
|
|
||||||
jinja2/__pycache__/nativetypes.cpython-37.pyc,,
|
|
|
@ -1,6 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py2-none-any
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[babel.extractors]
|
|
||||||
jinja2 = jinja2.ext:babel_extract [i18n]
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
jinja2
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,28 +0,0 @@
|
||||||
Copyright 2010 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,94 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: MarkupSafe
|
|
||||||
Version: 1.1.1
|
|
||||||
Summary: Safely add untrusted strings to HTML/XML markup.
|
|
||||||
Home-page: https://palletsprojects.com/p/markupsafe/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: The Pallets Team
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
|
||||||
Project-URL: Code, https://github.com/pallets/markupsafe
|
|
||||||
Project-URL: Issue tracker, https://github.com/pallets/markupsafe/issues
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
||||||
Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
|
|
||||||
MarkupSafe
|
|
||||||
==========
|
|
||||||
|
|
||||||
MarkupSafe implements a text object that escapes characters so it is
|
|
||||||
safe to use in HTML and XML. Characters that have special meanings are
|
|
||||||
replaced so that they display as the actual characters. This mitigates
|
|
||||||
injection attacks, meaning untrusted user input can safely be displayed
|
|
||||||
on a page.
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
pip install -U MarkupSafe
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. code-block:: pycon
|
|
||||||
|
|
||||||
>>> from markupsafe import Markup, escape
|
|
||||||
>>> # escape replaces special characters and wraps in Markup
|
|
||||||
>>> escape('<script>alert(document.cookie);</script>')
|
|
||||||
Markup(u'<script>alert(document.cookie);</script>')
|
|
||||||
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
|
||||||
>>> Markup('<strong>Hello</strong>')
|
|
||||||
Markup('<strong>hello</strong>')
|
|
||||||
>>> escape(Markup('<strong>Hello</strong>'))
|
|
||||||
Markup('<strong>hello</strong>')
|
|
||||||
>>> # Markup is a text subclass (str on Python 3, unicode on Python 2)
|
|
||||||
>>> # methods and operators escape their arguments
|
|
||||||
>>> template = Markup("Hello <em>%s</em>")
|
|
||||||
>>> template % '"World"'
|
|
||||||
Markup('Hello <em>"World"</em>')
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports MarkupSafe and other
|
|
||||||
libraries that use it. In order to grow the community of contributors
|
|
||||||
and users, and allow the maintainers to devote more time to the
|
|
||||||
projects, `please donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Website: https://palletsprojects.com/p/markupsafe/
|
|
||||||
* Documentation: https://markupsafe.palletsprojects.com/
|
|
||||||
* Releases: https://pypi.org/project/MarkupSafe/
|
|
||||||
* Code: https://github.com/pallets/markupsafe
|
|
||||||
* Issue tracker: https://github.com/pallets/markupsafe/issues
|
|
||||||
* Test status: https://dev.azure.com/pallets/markupsafe/_build
|
|
||||||
* Official chat: https://discord.gg/t6rrQZH
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
markupsafe/__init__.py,sha256=oTblO5f9KFM-pvnq9bB0HgElnqkJyqHnFN1Nx2NIvnY,10126
|
|
||||||
markupsafe/_compat.py,sha256=uEW1ybxEjfxIiuTbRRaJpHsPFf4yQUMMKaPgYEC5XbU,558
|
|
||||||
markupsafe/_constants.py,sha256=zo2ajfScG-l1Sb_52EP3MlDCqO7Y1BVHUXXKRsVDRNk,4690
|
|
||||||
markupsafe/_native.py,sha256=d-8S_zzYt2y512xYcuSxq0NeG2DUUvG80wVdTn-4KI8,1873
|
|
||||||
markupsafe/_speedups.c,sha256=k0fzEIK3CP6MmMqeY0ob43TP90mVN0DTyn7BAl3RqSg,9884
|
|
||||||
markupsafe/_speedups.cpython-37m-darwin.so,sha256=Rg1LaaphK67NRgvgR4b05sAu3ZC3dlJ1q1_GXe60rHQ,35072
|
|
||||||
MarkupSafe-1.1.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
|
||||||
MarkupSafe-1.1.1.dist-info/METADATA,sha256=-XXnVvCxQP2QbHutIQq_7Pk9OATy-x0NC7gN_3_SCRE,3167
|
|
||||||
MarkupSafe-1.1.1.dist-info/WHEEL,sha256=WVG6A_oY5zUoNXUyvfLMOSXC-hqC5VZacUL1MeSOz70,110
|
|
||||||
MarkupSafe-1.1.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
|
||||||
MarkupSafe-1.1.1.dist-info/RECORD,,
|
|
||||||
MarkupSafe-1.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
markupsafe/__pycache__/_native.cpython-37.pyc,,
|
|
||||||
markupsafe/__pycache__/_constants.cpython-37.pyc,,
|
|
||||||
markupsafe/__pycache__/_compat.cpython-37.pyc,,
|
|
||||||
markupsafe/__pycache__/__init__.cpython-37.pyc,,
|
|
|
@ -1,5 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: false
|
|
||||||
Tag: cp37-cp37m-macosx_10_9_x86_64
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
markupsafe
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2017-2021 Ingy döt Net
|
|
||||||
Copyright (c) 2006-2016 Kirill Simonov
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,46 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: PyYAML
|
|
||||||
Version: 5.4.1
|
|
||||||
Summary: YAML parser and emitter for Python
|
|
||||||
Home-page: https://pyyaml.org/
|
|
||||||
Author: Kirill Simonov
|
|
||||||
Author-email: xi@resolvent.net
|
|
||||||
License: MIT
|
|
||||||
Download-URL: https://pypi.org/project/PyYAML/
|
|
||||||
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
|
|
||||||
Project-URL: CI, https://github.com/yaml/pyyaml/actions
|
|
||||||
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
|
|
||||||
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
|
|
||||||
Project-URL: Source Code, https://github.com/yaml/pyyaml
|
|
||||||
Platform: Any
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Cython
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup
|
|
||||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
|
|
||||||
|
|
||||||
YAML is a data serialization format designed for human readability
|
|
||||||
and interaction with scripting languages. PyYAML is a YAML parser
|
|
||||||
and emitter for Python.
|
|
||||||
|
|
||||||
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
|
|
||||||
support, capable extension API, and sensible error messages. PyYAML
|
|
||||||
supports standard YAML tags and provides Python-specific tags that
|
|
||||||
allow to represent an arbitrary Python object.
|
|
||||||
|
|
||||||
PyYAML is applicable for a broad range of tasks from complex
|
|
||||||
configuration files to object serialization and persistence.
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
|
|
||||||
yaml/__init__.py,sha256=gfp2CbRVhzknghkiiJD2l6Z0pI-mv_iZHPSJ4aj0-nY,13170
|
|
||||||
yaml/_yaml.cpython-37m-darwin.so,sha256=prnbDrV8DfzEtBNgZK5Mtbkmh7G4UKDUA-Xx7F8ZfZs,448624
|
|
||||||
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
|
|
||||||
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
|
|
||||||
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
|
|
||||||
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
|
|
||||||
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
|
|
||||||
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
|
|
||||||
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
|
|
||||||
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
|
|
||||||
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
|
|
||||||
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
|
|
||||||
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
|
|
||||||
yaml/representer.py,sha256=82UM3ZxUQKqsKAF4ltWOxCS6jGPIFtXpGs7mvqyv4Xs,14184
|
|
||||||
yaml/resolver.py,sha256=Z1W8AOMA6Proy4gIO2OhUO4IPS_bFNAl0Ca3rwChpPg,8999
|
|
||||||
yaml/scanner.py,sha256=KeQIKGNlSyPE8QDwionHxy9CgbqE5teJEz05FR9-nAg,51277
|
|
||||||
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
|
|
||||||
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
|
|
||||||
yaml/__pycache__/__init__.cpython-37.pyc,sha256=BT2G6vuLqtl24Yazl_BTiWNVL_eWa463vBRw6EzkYnE,11736
|
|
||||||
yaml/__pycache__/composer.cpython-37.pyc,sha256=pUIfvPVwm6nQgfSqMoPDwWCHq0Q8tkvLqnV02x0LWK8,3497
|
|
||||||
yaml/__pycache__/constructor.cpython-37.pyc,sha256=juRoNZURP03EFLfHtLc7ksBZFaBX6rrFOh1h5ekOlzY,21223
|
|
||||||
yaml/__pycache__/cyaml.cpython-37.pyc,sha256=-Aa-eWnuxZ1JHVAPkQMVRzM0V3bJLDAT63kE8vLGcWM,3667
|
|
||||||
yaml/__pycache__/dumper.cpython-37.pyc,sha256=2sX7qVZ5lWxv6Upb87sBTeyRIsKyKgKKposCA32VOpU,2037
|
|
||||||
yaml/__pycache__/emitter.cpython-37.pyc,sha256=d6xBEGmZM9dppMH71xFASgCGTBkwNOSk7hFu0c77td4,25287
|
|
||||||
yaml/__pycache__/error.cpython-37.pyc,sha256=ixzwceH8Hj5SQP-K7EpSdATWbZnYqSZ-9ggbZql3lTc,2248
|
|
||||||
yaml/__pycache__/events.cpython-37.pyc,sha256=BsC1S1A7MIeiZTXniRm64kBKf7eNEb72wgSyZa9wdWs,4014
|
|
||||||
yaml/__pycache__/loader.cpython-37.pyc,sha256=z0Z_0wFNq4wC93xRz3xXVgKXSyeJo_9NlSP_KM-Q258,2246
|
|
||||||
yaml/__pycache__/nodes.cpython-37.pyc,sha256=PhNPeBWTYDzIKngO556mJhx_BIoDnAIbVaQH9j5qoTg,1692
|
|
||||||
yaml/__pycache__/parser.cpython-37.pyc,sha256=f8nrupPuPBu640bHCxw1upOI1gleGD0OnZO61Ke4w44,11842
|
|
||||||
yaml/__pycache__/reader.cpython-37.pyc,sha256=Evtv1Je-xmXe2cn6llqwDthhhEGo7xSqtt-X3H4yh6c,4455
|
|
||||||
yaml/__pycache__/representer.cpython-37.pyc,sha256=7Toj31yd3JI883VhAJNo906zwYS_1OPCtcRS0o7VXEU,10078
|
|
||||||
yaml/__pycache__/resolver.cpython-37.pyc,sha256=ToRLdIZnr2UCCnlzoUJeYjF_5LjZfdZQJALM5dao_RI,5448
|
|
||||||
yaml/__pycache__/scanner.cpython-37.pyc,sha256=1BS76EIvYaP8-Dv5O2GbI25Y3mTb60yfvn4hyH2G4RM,25788
|
|
||||||
yaml/__pycache__/serializer.cpython-37.pyc,sha256=rckMp9nvrgGLLpSSEkenUmPxSe9C4s_DWkmbh8Ayvgs,3327
|
|
||||||
yaml/__pycache__/tokens.cpython-37.pyc,sha256=mDKFX-_apCXzBM7oHVvqfbvGSsqcjcMbqzuU3TlVd0g,5191
|
|
||||||
PyYAML-5.4.1.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
|
||||||
PyYAML-5.4.1.dist-info/METADATA,sha256=XnrM5LY-uS85ica26gKUK0dGG-xmPjmGfDTSLpIHQFk,2087
|
|
||||||
PyYAML-5.4.1.dist-info/WHEEL,sha256=WVG6A_oY5zUoNXUyvfLMOSXC-hqC5VZacUL1MeSOz70,110
|
|
||||||
PyYAML-5.4.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
|
|
||||||
PyYAML-5.4.1.dist-info/RECORD,,
|
|
||||||
PyYAML-5.4.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
_yaml/__pycache__/__init__.cpython-37.pyc,,
|
|
|
@ -1,5 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: false
|
|
||||||
Tag: cp37-cp37m-macosx_10_9_x86_64
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
_yaml
|
|
||||||
yaml
|
|
Binary file not shown.
|
@ -1,33 +0,0 @@
|
||||||
# This is a stub package designed to roughly emulate the _yaml
|
|
||||||
# extension module, which previously existed as a standalone module
|
|
||||||
# and has been moved into the `yaml` package namespace.
|
|
||||||
# It does not perfectly mimic its old counterpart, but should get
|
|
||||||
# close enough for anyone who's relying on it even when they shouldn't.
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
|
||||||
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
|
||||||
if not getattr(yaml, '__with_libyaml__', False):
|
|
||||||
from sys import version_info
|
|
||||||
|
|
||||||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
|
||||||
raise exc("No module named '_yaml'")
|
|
||||||
else:
|
|
||||||
from yaml._yaml import *
|
|
||||||
import warnings
|
|
||||||
warnings.warn(
|
|
||||||
'The _yaml extension module is now located at yaml._yaml'
|
|
||||||
' and its location is subject to change. To use the'
|
|
||||||
' LibYAML-based parser and emitter, import from `yaml`:'
|
|
||||||
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
del warnings
|
|
||||||
# Don't `del yaml` here because yaml is actually an existing
|
|
||||||
# namespace member of _yaml.
|
|
||||||
|
|
||||||
__name__ = '_yaml'
|
|
||||||
# If the module is top-level (i.e. not a part of any specific package)
|
|
||||||
# then the attribute should be set to ''.
|
|
||||||
# https://docs.python.org/3.8/library/types.html
|
|
||||||
__package__ = ''
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,201 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2013-2020 aiohttp maintainers
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,964 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: aiohttp
|
|
||||||
Version: 3.7.4
|
|
||||||
Summary: Async http client/server framework (asyncio)
|
|
||||||
Home-page: https://github.com/aio-libs/aiohttp
|
|
||||||
Author: Nikolay Kim
|
|
||||||
Author-email: fafhrd91@gmail.com
|
|
||||||
Maintainer: Nikolay Kim <fafhrd91@gmail.com>, Andrew Svetlov <andrew.svetlov@gmail.com>
|
|
||||||
Maintainer-email: aio-libs@googlegroups.com
|
|
||||||
License: Apache 2
|
|
||||||
Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby
|
|
||||||
Project-URL: CI: Azure Pipelines, https://dev.azure.com/aio-libs/aiohttp/_build
|
|
||||||
Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiohttp
|
|
||||||
Project-URL: Docs: RTD, https://docs.aiohttp.org
|
|
||||||
Project-URL: GitHub: issues, https://github.com/aio-libs/aiohttp/issues
|
|
||||||
Project-URL: GitHub: repo, https://github.com/aio-libs/aiohttp
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: License :: OSI Approved :: Apache Software License
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Operating System :: POSIX
|
|
||||||
Classifier: Operating System :: MacOS :: MacOS X
|
|
||||||
Classifier: Operating System :: Microsoft :: Windows
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP
|
|
||||||
Classifier: Framework :: AsyncIO
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Requires-Dist: attrs (>=17.3.0)
|
|
||||||
Requires-Dist: chardet (<4.0,>=2.0)
|
|
||||||
Requires-Dist: multidict (<7.0,>=4.5)
|
|
||||||
Requires-Dist: async-timeout (<4.0,>=3.0)
|
|
||||||
Requires-Dist: yarl (<2.0,>=1.0)
|
|
||||||
Requires-Dist: typing-extensions (>=3.6.5)
|
|
||||||
Requires-Dist: idna-ssl (>=1.0) ; python_version < "3.7"
|
|
||||||
Provides-Extra: speedups
|
|
||||||
Requires-Dist: aiodns ; extra == 'speedups'
|
|
||||||
Requires-Dist: brotlipy ; extra == 'speedups'
|
|
||||||
Requires-Dist: cchardet ; extra == 'speedups'
|
|
||||||
|
|
||||||
==================================
|
|
||||||
Async http client/server framework
|
|
||||||
==================================
|
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/_static/aiohttp-icon-128x128.png
|
|
||||||
:height: 64px
|
|
||||||
:width: 64px
|
|
||||||
:alt: aiohttp logo
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
.. image:: https://github.com/aio-libs/aiohttp/workflows/CI/badge.svg
|
|
||||||
:target: https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
|
|
||||||
:alt: GitHub Actions status for master branch
|
|
||||||
|
|
||||||
.. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg
|
|
||||||
:target: https://codecov.io/gh/aio-libs/aiohttp
|
|
||||||
:alt: codecov.io status for master branch
|
|
||||||
|
|
||||||
.. image:: https://badge.fury.io/py/aiohttp.svg
|
|
||||||
:target: https://pypi.org/project/aiohttp
|
|
||||||
:alt: Latest PyPI package version
|
|
||||||
|
|
||||||
.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest
|
|
||||||
:target: https://docs.aiohttp.org/
|
|
||||||
:alt: Latest Read The Docs
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/discourse/status?server=https%3A%2F%2Faio-libs.discourse.group
|
|
||||||
:target: https://aio-libs.discourse.group
|
|
||||||
:alt: Discourse status
|
|
||||||
|
|
||||||
.. image:: https://badges.gitter.im/Join%20Chat.svg
|
|
||||||
:target: https://gitter.im/aio-libs/Lobby
|
|
||||||
:alt: Chat on Gitter
|
|
||||||
|
|
||||||
|
|
||||||
Key Features
|
|
||||||
============
|
|
||||||
|
|
||||||
- Supports both client and server side of HTTP protocol.
|
|
||||||
- Supports both client and server Web-Sockets out-of-the-box and avoids
|
|
||||||
Callback Hell.
|
|
||||||
- Provides Web-server with middlewares and plugable routing.
|
|
||||||
|
|
||||||
|
|
||||||
Getting started
|
|
||||||
===============
|
|
||||||
|
|
||||||
Client
|
|
||||||
------
|
|
||||||
|
|
||||||
To get something from the web:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get('http://python.org') as response:
|
|
||||||
|
|
||||||
print("Status:", response.status)
|
|
||||||
print("Content-type:", response.headers['content-type'])
|
|
||||||
|
|
||||||
html = await response.text()
|
|
||||||
print("Body:", html[:15], "...")
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
||||||
|
|
||||||
This prints:
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
|
|
||||||
Status: 200
|
|
||||||
Content-type: text/html; charset=utf-8
|
|
||||||
Body: <!doctype html> ...
|
|
||||||
|
|
||||||
Coming from `requests <https://requests.readthedocs.io/>`_ ? Read `why we need so many lines <https://aiohttp.readthedocs.io/en/latest/http_request_lifecycle.html>`_.
|
|
||||||
|
|
||||||
Server
|
|
||||||
------
|
|
||||||
|
|
||||||
An example using a simple server:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# examples/server_simple.py
|
|
||||||
from aiohttp import web
|
|
||||||
|
|
||||||
async def handle(request):
|
|
||||||
name = request.match_info.get('name', "Anonymous")
|
|
||||||
text = "Hello, " + name
|
|
||||||
return web.Response(text=text)
|
|
||||||
|
|
||||||
async def wshandle(request):
|
|
||||||
ws = web.WebSocketResponse()
|
|
||||||
await ws.prepare(request)
|
|
||||||
|
|
||||||
async for msg in ws:
|
|
||||||
if msg.type == web.WSMsgType.text:
|
|
||||||
await ws.send_str("Hello, {}".format(msg.data))
|
|
||||||
elif msg.type == web.WSMsgType.binary:
|
|
||||||
await ws.send_bytes(msg.data)
|
|
||||||
elif msg.type == web.WSMsgType.close:
|
|
||||||
break
|
|
||||||
|
|
||||||
return ws
|
|
||||||
|
|
||||||
|
|
||||||
app = web.Application()
|
|
||||||
app.add_routes([web.get('/', handle),
|
|
||||||
web.get('/echo', wshandle),
|
|
||||||
web.get('/{name}', handle)])
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
web.run_app(app)
|
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
=============
|
|
||||||
|
|
||||||
https://aiohttp.readthedocs.io/
|
|
||||||
|
|
||||||
|
|
||||||
Demos
|
|
||||||
=====
|
|
||||||
|
|
||||||
https://github.com/aio-libs/aiohttp-demos
|
|
||||||
|
|
||||||
|
|
||||||
External links
|
|
||||||
==============
|
|
||||||
|
|
||||||
* `Third party libraries
|
|
||||||
<http://aiohttp.readthedocs.io/en/latest/third_party.html>`_
|
|
||||||
* `Built with aiohttp
|
|
||||||
<http://aiohttp.readthedocs.io/en/latest/built_with.html>`_
|
|
||||||
* `Powered by aiohttp
|
|
||||||
<http://aiohttp.readthedocs.io/en/latest/powered_by.html>`_
|
|
||||||
|
|
||||||
Feel free to make a Pull Request for adding your link to these pages!
|
|
||||||
|
|
||||||
|
|
||||||
Communication channels
|
|
||||||
======================
|
|
||||||
|
|
||||||
*aio-libs discourse group*: https://aio-libs.discourse.group
|
|
||||||
|
|
||||||
*gitter chat* https://gitter.im/aio-libs/Lobby
|
|
||||||
|
|
||||||
We support `Stack Overflow
|
|
||||||
<https://stackoverflow.com/questions/tagged/aiohttp>`_.
|
|
||||||
Please add *aiohttp* tag to your question there.
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
============
|
|
||||||
|
|
||||||
- Python >= 3.6
|
|
||||||
- async-timeout_
|
|
||||||
- attrs_
|
|
||||||
- chardet_
|
|
||||||
- multidict_
|
|
||||||
- yarl_
|
|
||||||
|
|
||||||
Optionally you may install the cChardet_ and aiodns_ libraries (highly
|
|
||||||
recommended for sake of speed).
|
|
||||||
|
|
||||||
.. _chardet: https://pypi.python.org/pypi/chardet
|
|
||||||
.. _aiodns: https://pypi.python.org/pypi/aiodns
|
|
||||||
.. _attrs: https://github.com/python-attrs/attrs
|
|
||||||
.. _multidict: https://pypi.python.org/pypi/multidict
|
|
||||||
.. _yarl: https://pypi.python.org/pypi/yarl
|
|
||||||
.. _async-timeout: https://pypi.python.org/pypi/async_timeout
|
|
||||||
.. _cChardet: https://pypi.python.org/pypi/cchardet
|
|
||||||
|
|
||||||
License
|
|
||||||
=======
|
|
||||||
|
|
||||||
``aiohttp`` is offered under the Apache 2 license.
|
|
||||||
|
|
||||||
|
|
||||||
Keepsafe
|
|
||||||
========
|
|
||||||
|
|
||||||
The aiohttp community would like to thank Keepsafe
|
|
||||||
(https://www.getkeepsafe.com) for its support in the early days of
|
|
||||||
the project.
|
|
||||||
|
|
||||||
|
|
||||||
Source code
|
|
||||||
===========
|
|
||||||
|
|
||||||
The latest developer version is available in a GitHub repository:
|
|
||||||
https://github.com/aio-libs/aiohttp
|
|
||||||
|
|
||||||
Benchmarks
|
|
||||||
==========
|
|
||||||
|
|
||||||
If you are interested in efficiency, the AsyncIO community maintains a
|
|
||||||
list of benchmarks on the official wiki:
|
|
||||||
https://github.com/python/asyncio/wiki/Benchmarks
|
|
||||||
|
|
||||||
=========
|
|
||||||
Changelog
|
|
||||||
=========
|
|
||||||
|
|
||||||
..
|
|
||||||
You should *NOT* be adding new change log entries to this file, this
|
|
||||||
file is managed by towncrier. You *may* edit previous change logs to
|
|
||||||
fix problems like typo corrections or such.
|
|
||||||
To add a new change log entry, please see
|
|
||||||
https://pip.pypa.io/en/latest/development/#adding-a-news-entry
|
|
||||||
we named the news folder "changes".
|
|
||||||
|
|
||||||
WARNING: Don't drop the next directive!
|
|
||||||
|
|
||||||
.. towncrier release notes start
|
|
||||||
|
|
||||||
3.7.3 (2021-02-25)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- **(SECURITY BUG)** Started preventing open redirects in the
|
|
||||||
``aiohttp.web.normalize_path_middleware`` middleware. For
|
|
||||||
more details, see
|
|
||||||
https://github.com/aio-libs/aiohttp/security/advisories/GHSA-v6wp-4m6f-gcjg.
|
|
||||||
|
|
||||||
Thanks to `Beast Glatisant <https://github.com/g147>`__ for
|
|
||||||
finding the first instance of this issue and `Jelmer Vernooij
|
|
||||||
<https://jelmer.uk/>`__ for reporting and tracking it down
|
|
||||||
in aiohttp.
|
|
||||||
`#5497 <https://github.com/aio-libs/aiohttp/issues/5497>`_
|
|
||||||
- Fix interpretation difference of the pure-Python and the Cython-based
|
|
||||||
HTTP parsers construct a ``yarl.URL`` object for HTTP request-target.
|
|
||||||
|
|
||||||
Before this fix, the Python parser would turn the URI's absolute-path
|
|
||||||
for ``//some-path`` into ``/`` while the Cython code preserved it as
|
|
||||||
``//some-path``. Now, both do the latter.
|
|
||||||
`#5498 <https://github.com/aio-libs/aiohttp/issues/5498>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.7.3 (2020-11-18)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Use Brotli instead of brotlipy
|
|
||||||
`#3803 <https://github.com/aio-libs/aiohttp/issues/3803>`_
|
|
||||||
- Made exceptions pickleable. Also changed the repr of some exceptions.
|
|
||||||
`#4077 <https://github.com/aio-libs/aiohttp/issues/4077>`_
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Raise a ClientResponseError instead of an AssertionError for a blank
|
|
||||||
HTTP Reason Phrase.
|
|
||||||
`#3532 <https://github.com/aio-libs/aiohttp/issues/3532>`_
|
|
||||||
- Fix ``web_middlewares.normalize_path_middleware`` behavior for patch without slash.
|
|
||||||
`#3669 <https://github.com/aio-libs/aiohttp/issues/3669>`_
|
|
||||||
- Fix overshadowing of overlapped sub-applications prefixes.
|
|
||||||
`#3701 <https://github.com/aio-libs/aiohttp/issues/3701>`_
|
|
||||||
- Make `BaseConnector.close()` a coroutine and wait until the client closes all connections. Drop deprecated "with Connector():" syntax.
|
|
||||||
`#3736 <https://github.com/aio-libs/aiohttp/issues/3736>`_
|
|
||||||
- Reset the ``sock_read`` timeout each time data is received for a ``aiohttp.client`` response.
|
|
||||||
`#3808 <https://github.com/aio-libs/aiohttp/issues/3808>`_
|
|
||||||
- Fixed type annotation for add_view method of UrlDispatcher to accept any subclass of View
|
|
||||||
`#3880 <https://github.com/aio-libs/aiohttp/issues/3880>`_
|
|
||||||
- Fixed querying the address families from DNS that the current host supports.
|
|
||||||
`#5156 <https://github.com/aio-libs/aiohttp/issues/5156>`_
|
|
||||||
- Change return type of MultipartReader.__aiter__() and BodyPartReader.__aiter__() to AsyncIterator.
|
|
||||||
`#5163 <https://github.com/aio-libs/aiohttp/issues/5163>`_
|
|
||||||
- Provide x86 Windows wheels.
|
|
||||||
`#5230 <https://github.com/aio-libs/aiohttp/issues/5230>`_
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Add documentation for ``aiohttp.web.FileResponse``.
|
|
||||||
`#3958 <https://github.com/aio-libs/aiohttp/issues/3958>`_
|
|
||||||
- Removed deprecation warning in tracing example docs
|
|
||||||
`#3964 <https://github.com/aio-libs/aiohttp/issues/3964>`_
|
|
||||||
- Fixed wrong "Usage" docstring of ``aiohttp.client.request``.
|
|
||||||
`#4603 <https://github.com/aio-libs/aiohttp/issues/4603>`_
|
|
||||||
- Add aiohttp-pydantic to third party libraries
|
|
||||||
`#5228 <https://github.com/aio-libs/aiohttp/issues/5228>`_
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- `#4102 <https://github.com/aio-libs/aiohttp/issues/4102>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.7.2 (2020-10-27)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Fixed static files handling for loops without ``.sendfile()`` support
|
|
||||||
`#5149 <https://github.com/aio-libs/aiohttp/issues/5149>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.7.1 (2020-10-25)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Fixed a type error caused by the conditional import of `Protocol`.
|
|
||||||
`#5111 <https://github.com/aio-libs/aiohttp/issues/5111>`_
|
|
||||||
- Server doesn't send Content-Length for 1xx or 204
|
|
||||||
`#4901 <https://github.com/aio-libs/aiohttp/issues/4901>`_
|
|
||||||
- Fix run_app typing
|
|
||||||
`#4957 <https://github.com/aio-libs/aiohttp/issues/4957>`_
|
|
||||||
- Always require ``typing_extensions`` library.
|
|
||||||
`#5107 <https://github.com/aio-libs/aiohttp/issues/5107>`_
|
|
||||||
- Fix a variable-shadowing bug causing `ThreadedResolver.resolve` to
|
|
||||||
return the resolved IP as the ``hostname`` in each record, which prevented
|
|
||||||
validation of HTTPS connections.
|
|
||||||
`#5110 <https://github.com/aio-libs/aiohttp/issues/5110>`_
|
|
||||||
- Added annotations to all public attributes.
|
|
||||||
`#5115 <https://github.com/aio-libs/aiohttp/issues/5115>`_
|
|
||||||
- Fix flaky test_when_timeout_smaller_second
|
|
||||||
`#5116 <https://github.com/aio-libs/aiohttp/issues/5116>`_
|
|
||||||
- Ensure sending a zero byte file does not throw an exception
|
|
||||||
`#5124 <https://github.com/aio-libs/aiohttp/issues/5124>`_
|
|
||||||
- Fix a bug in ``web.run_app()`` about Python version checking on Windows
|
|
||||||
`#5127 <https://github.com/aio-libs/aiohttp/issues/5127>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.7.0 (2020-10-24)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Response headers are now prepared prior to running ``on_response_prepare`` hooks, directly before headers are sent to the client.
|
|
||||||
`#1958 <https://github.com/aio-libs/aiohttp/issues/1958>`_
|
|
||||||
- Add a ``quote_cookie`` option to ``CookieJar``, a way to skip quotation wrapping of cookies containing special characters.
|
|
||||||
`#2571 <https://github.com/aio-libs/aiohttp/issues/2571>`_
|
|
||||||
- Call ``AccessLogger.log`` with the current exception available from ``sys.exc_info()``.
|
|
||||||
`#3557 <https://github.com/aio-libs/aiohttp/issues/3557>`_
|
|
||||||
- `web.UrlDispatcher.add_routes` and `web.Application.add_routes` return a list
|
|
||||||
of registered `AbstractRoute` instances. `AbstractRouteDef.register` (and all
|
|
||||||
subclasses) return a list of registered resources registered resource.
|
|
||||||
`#3866 <https://github.com/aio-libs/aiohttp/issues/3866>`_
|
|
||||||
- Added properties of default ClientSession params to ClientSession class so it is available for introspection
|
|
||||||
`#3882 <https://github.com/aio-libs/aiohttp/issues/3882>`_
|
|
||||||
- Don't cancel web handler on peer disconnection, raise `OSError` on reading/writing instead.
|
|
||||||
`#4080 <https://github.com/aio-libs/aiohttp/issues/4080>`_
|
|
||||||
- Implement BaseRequest.get_extra_info() to access a protocol transports' extra info.
|
|
||||||
`#4189 <https://github.com/aio-libs/aiohttp/issues/4189>`_
|
|
||||||
- Added `ClientSession.timeout` property.
|
|
||||||
`#4191 <https://github.com/aio-libs/aiohttp/issues/4191>`_
|
|
||||||
- allow use of SameSite in cookies.
|
|
||||||
`#4224 <https://github.com/aio-libs/aiohttp/issues/4224>`_
|
|
||||||
- Use ``loop.sendfile()`` instead of custom implementation if available.
|
|
||||||
`#4269 <https://github.com/aio-libs/aiohttp/issues/4269>`_
|
|
||||||
- Apply SO_REUSEADDR to test server's socket.
|
|
||||||
`#4393 <https://github.com/aio-libs/aiohttp/issues/4393>`_
|
|
||||||
- Use .raw_host instead of slower .host in client API
|
|
||||||
`#4402 <https://github.com/aio-libs/aiohttp/issues/4402>`_
|
|
||||||
- Allow configuring the buffer size of input stream by passing ``read_bufsize`` argument.
|
|
||||||
`#4453 <https://github.com/aio-libs/aiohttp/issues/4453>`_
|
|
||||||
- Pass tests on Python 3.8 for Windows.
|
|
||||||
`#4513 <https://github.com/aio-libs/aiohttp/issues/4513>`_
|
|
||||||
- Add `method` and `url` attributes to `TraceRequestChunkSentParams` and `TraceResponseChunkReceivedParams`.
|
|
||||||
`#4674 <https://github.com/aio-libs/aiohttp/issues/4674>`_
|
|
||||||
- Add ClientResponse.ok property for checking status code under 400.
|
|
||||||
`#4711 <https://github.com/aio-libs/aiohttp/issues/4711>`_
|
|
||||||
- Don't ceil timeouts that are smaller than 5 seconds.
|
|
||||||
`#4850 <https://github.com/aio-libs/aiohttp/issues/4850>`_
|
|
||||||
- TCPSite now listens by default on all interfaces instead of just IPv4 when `None` is passed in as the host.
|
|
||||||
`#4894 <https://github.com/aio-libs/aiohttp/issues/4894>`_
|
|
||||||
- Bump ``http_parser`` to 2.9.4
|
|
||||||
`#5070 <https://github.com/aio-libs/aiohttp/issues/5070>`_
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Fix keepalive connections not being closed in time
|
|
||||||
`#3296 <https://github.com/aio-libs/aiohttp/issues/3296>`_
|
|
||||||
- Fix failed websocket handshake leaving connection hanging.
|
|
||||||
`#3380 <https://github.com/aio-libs/aiohttp/issues/3380>`_
|
|
||||||
- Fix tasks cancellation order on exit. The run_app task needs to be cancelled first for cleanup hooks to run with all tasks intact.
|
|
||||||
`#3805 <https://github.com/aio-libs/aiohttp/issues/3805>`_
|
|
||||||
- Don't start heartbeat until _writer is set
|
|
||||||
`#4062 <https://github.com/aio-libs/aiohttp/issues/4062>`_
|
|
||||||
- Fix handling of multipart file uploads without a content type.
|
|
||||||
`#4089 <https://github.com/aio-libs/aiohttp/issues/4089>`_
|
|
||||||
- Preserve view handler function attributes across middlewares
|
|
||||||
`#4174 <https://github.com/aio-libs/aiohttp/issues/4174>`_
|
|
||||||
- Fix the string representation of ``ServerDisconnectedError``.
|
|
||||||
`#4175 <https://github.com/aio-libs/aiohttp/issues/4175>`_
|
|
||||||
- Raising RuntimeError when trying to get encoding from not read body
|
|
||||||
`#4214 <https://github.com/aio-libs/aiohttp/issues/4214>`_
|
|
||||||
- Remove warning messages from noop.
|
|
||||||
`#4282 <https://github.com/aio-libs/aiohttp/issues/4282>`_
|
|
||||||
- Raise ClientPayloadError if FormData re-processed.
|
|
||||||
`#4345 <https://github.com/aio-libs/aiohttp/issues/4345>`_
|
|
||||||
- Fix a warning about unfinished task in ``web_protocol.py``
|
|
||||||
`#4408 <https://github.com/aio-libs/aiohttp/issues/4408>`_
|
|
||||||
- Fixed 'deflate' compression. According to RFC 2616 now.
|
|
||||||
`#4506 <https://github.com/aio-libs/aiohttp/issues/4506>`_
|
|
||||||
- Fixed OverflowError on platforms with 32-bit time_t
|
|
||||||
`#4515 <https://github.com/aio-libs/aiohttp/issues/4515>`_
|
|
||||||
- Fixed request.body_exists returns wrong value for methods without body.
|
|
||||||
`#4528 <https://github.com/aio-libs/aiohttp/issues/4528>`_
|
|
||||||
- Fix connecting to link-local IPv6 addresses.
|
|
||||||
`#4554 <https://github.com/aio-libs/aiohttp/issues/4554>`_
|
|
||||||
- Fix a problem with connection waiters that are never awaited.
|
|
||||||
`#4562 <https://github.com/aio-libs/aiohttp/issues/4562>`_
|
|
||||||
- Always make sure transport is not closing before reuse a connection.
|
|
||||||
|
|
||||||
Reuse a protocol based on keepalive in headers is unreliable.
|
|
||||||
For example, uWSGI will not support keepalive even it serves a
|
|
||||||
HTTP 1.1 request, except explicitly configure uWSGI with a
|
|
||||||
``--http-keepalive`` option.
|
|
||||||
|
|
||||||
Servers designed like uWSGI could cause aiohttp intermittently
|
|
||||||
raise a ConnectionResetException when the protocol poll runs
|
|
||||||
out and some protocol is reused.
|
|
||||||
`#4587 <https://github.com/aio-libs/aiohttp/issues/4587>`_
|
|
||||||
- Handle the last CRLF correctly even if it is received via separate TCP segment.
|
|
||||||
`#4630 <https://github.com/aio-libs/aiohttp/issues/4630>`_
|
|
||||||
- Fix the register_resource function to validate route name before splitting it so that route name can include python keywords.
|
|
||||||
`#4691 <https://github.com/aio-libs/aiohttp/issues/4691>`_
|
|
||||||
- Improve typing annotations for ``web.Request``, ``aiohttp.ClientResponse`` and
|
|
||||||
``multipart`` module.
|
|
||||||
`#4736 <https://github.com/aio-libs/aiohttp/issues/4736>`_
|
|
||||||
- Fix resolver task is not awaited when connector is cancelled
|
|
||||||
`#4795 <https://github.com/aio-libs/aiohttp/issues/4795>`_
|
|
||||||
- Fix a bug "Aiohttp doesn't return any error on invalid request methods"
|
|
||||||
`#4798 <https://github.com/aio-libs/aiohttp/issues/4798>`_
|
|
||||||
- Fix HEAD requests for static content.
|
|
||||||
`#4809 <https://github.com/aio-libs/aiohttp/issues/4809>`_
|
|
||||||
- Fix incorrect size calculation for memoryview
|
|
||||||
`#4890 <https://github.com/aio-libs/aiohttp/issues/4890>`_
|
|
||||||
- Add HTTPMove to _all__.
|
|
||||||
`#4897 <https://github.com/aio-libs/aiohttp/issues/4897>`_
|
|
||||||
- Fixed the type annotations in the ``tracing`` module.
|
|
||||||
`#4912 <https://github.com/aio-libs/aiohttp/issues/4912>`_
|
|
||||||
- Fix typing for multipart ``__aiter__``.
|
|
||||||
`#4931 <https://github.com/aio-libs/aiohttp/issues/4931>`_
|
|
||||||
- Fix for race condition on connections in BaseConnector that leads to exceeding the connection limit.
|
|
||||||
`#4936 <https://github.com/aio-libs/aiohttp/issues/4936>`_
|
|
||||||
- Add forced UTF-8 encoding for ``application/rdap+json`` responses.
|
|
||||||
`#4938 <https://github.com/aio-libs/aiohttp/issues/4938>`_
|
|
||||||
- Fix inconsistency between Python and C http request parsers in parsing pct-encoded URL.
|
|
||||||
`#4972 <https://github.com/aio-libs/aiohttp/issues/4972>`_
|
|
||||||
- Fix connection closing issue in HEAD request.
|
|
||||||
`#5012 <https://github.com/aio-libs/aiohttp/issues/5012>`_
|
|
||||||
- Fix type hint on BaseRunner.addresses (from ``List[str]`` to ``List[Any]``)
|
|
||||||
`#5086 <https://github.com/aio-libs/aiohttp/issues/5086>`_
|
|
||||||
- Make `web.run_app()` more responsive to Ctrl+C on Windows for Python < 3.8. It slightly
|
|
||||||
increases CPU load as a side effect.
|
|
||||||
`#5098 <https://github.com/aio-libs/aiohttp/issues/5098>`_
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Fix example code in client quick-start
|
|
||||||
`#3376 <https://github.com/aio-libs/aiohttp/issues/3376>`_
|
|
||||||
- Updated the docs so there is no contradiction in ``ttl_dns_cache`` default value
|
|
||||||
`#3512 <https://github.com/aio-libs/aiohttp/issues/3512>`_
|
|
||||||
- Add 'Deploy with SSL' to docs.
|
|
||||||
`#4201 <https://github.com/aio-libs/aiohttp/issues/4201>`_
|
|
||||||
- Change typing of the secure argument on StreamResponse.set_cookie from ``Optional[str]`` to ``Optional[bool]``
|
|
||||||
`#4204 <https://github.com/aio-libs/aiohttp/issues/4204>`_
|
|
||||||
- Changes ``ttl_dns_cache`` type from int to Optional[int].
|
|
||||||
`#4270 <https://github.com/aio-libs/aiohttp/issues/4270>`_
|
|
||||||
- Simplify README hello word example and add a documentation page for people coming from requests.
|
|
||||||
`#4272 <https://github.com/aio-libs/aiohttp/issues/4272>`_
|
|
||||||
- Improve some code examples in the documentation involving websockets and starting a simple HTTP site with an AppRunner.
|
|
||||||
`#4285 <https://github.com/aio-libs/aiohttp/issues/4285>`_
|
|
||||||
- Fix typo in code example in Multipart docs
|
|
||||||
`#4312 <https://github.com/aio-libs/aiohttp/issues/4312>`_
|
|
||||||
- Fix code example in Multipart section.
|
|
||||||
`#4314 <https://github.com/aio-libs/aiohttp/issues/4314>`_
|
|
||||||
- Update contributing guide so new contributors read the most recent version of that guide. Update command used to create test coverage reporting.
|
|
||||||
`#4810 <https://github.com/aio-libs/aiohttp/issues/4810>`_
|
|
||||||
- Spelling: Change "canonize" to "canonicalize".
|
|
||||||
`#4986 <https://github.com/aio-libs/aiohttp/issues/4986>`_
|
|
||||||
- Add ``aiohttp-sse-client`` library to third party usage list.
|
|
||||||
`#5084 <https://github.com/aio-libs/aiohttp/issues/5084>`_
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- `#2856 <https://github.com/aio-libs/aiohttp/issues/2856>`_, `#4218 <https://github.com/aio-libs/aiohttp/issues/4218>`_, `#4250 <https://github.com/aio-libs/aiohttp/issues/4250>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.6.3 (2020-10-12)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Pin yarl to ``<1.6.0`` to avoid buggy behavior that will be fixed by the next aiohttp
|
|
||||||
release.
|
|
||||||
|
|
||||||
3.6.2 (2019-10-09)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Made exceptions pickleable. Also changed the repr of some exceptions.
|
|
||||||
`#4077 <https://github.com/aio-libs/aiohttp/issues/4077>`_
|
|
||||||
- Use ``Iterable`` type hint instead of ``Sequence`` for ``Application`` *middleware*
|
|
||||||
parameter. `#4125 <https://github.com/aio-libs/aiohttp/issues/4125>`_
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Reset the ``sock_read`` timeout each time data is received for a
|
|
||||||
``aiohttp.ClientResponse``. `#3808
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3808>`_
|
|
||||||
- Fix handling of expired cookies so they are not stored in CookieJar.
|
|
||||||
`#4063 <https://github.com/aio-libs/aiohttp/issues/4063>`_
|
|
||||||
- Fix misleading message in the string representation of ``ClientConnectorError``;
|
|
||||||
``self.ssl == None`` means default SSL context, not SSL disabled `#4097
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/4097>`_
|
|
||||||
- Don't clobber HTTP status when using FileResponse.
|
|
||||||
`#4106 <https://github.com/aio-libs/aiohttp/issues/4106>`_
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Added minimal required logging configuration to logging documentation.
|
|
||||||
`#2469 <https://github.com/aio-libs/aiohttp/issues/2469>`_
|
|
||||||
- Update docs to reflect proxy support.
|
|
||||||
`#4100 <https://github.com/aio-libs/aiohttp/issues/4100>`_
|
|
||||||
- Fix typo in code example in testing docs.
|
|
||||||
`#4108 <https://github.com/aio-libs/aiohttp/issues/4108>`_
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- `#4102 <https://github.com/aio-libs/aiohttp/issues/4102>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.6.1 (2019-09-19)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Compatibility with Python 3.8.
|
|
||||||
`#4056 <https://github.com/aio-libs/aiohttp/issues/4056>`_
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- correct some exception string format
|
|
||||||
`#4068 <https://github.com/aio-libs/aiohttp/issues/4068>`_
|
|
||||||
- Emit a warning when ``ssl.OP_NO_COMPRESSION`` is
|
|
||||||
unavailable because the runtime is built against
|
|
||||||
an outdated OpenSSL.
|
|
||||||
`#4052 <https://github.com/aio-libs/aiohttp/issues/4052>`_
|
|
||||||
- Update multidict requirement to >= 4.5
|
|
||||||
`#4057 <https://github.com/aio-libs/aiohttp/issues/4057>`_
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Provide pytest-aiohttp namespace for pytest fixtures in docs.
|
|
||||||
`#3723 <https://github.com/aio-libs/aiohttp/issues/3723>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.6.0 (2019-09-06)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Add support for Named Pipes (Site and Connector) under Windows. This feature requires
|
|
||||||
Proactor event loop to work. `#3629
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3629>`_
|
|
||||||
- Removed ``Transfer-Encoding: chunked`` header from websocket responses to be
|
|
||||||
compatible with more http proxy servers. `#3798
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3798>`_
|
|
||||||
- Accept non-GET request for starting websocket handshake on server side.
|
|
||||||
`#3980 <https://github.com/aio-libs/aiohttp/issues/3980>`_
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Raise a ClientResponseError instead of an AssertionError for a blank
|
|
||||||
HTTP Reason Phrase.
|
|
||||||
`#3532 <https://github.com/aio-libs/aiohttp/issues/3532>`_
|
|
||||||
- Fix an issue where cookies would sometimes not be set during a redirect.
|
|
||||||
`#3576 <https://github.com/aio-libs/aiohttp/issues/3576>`_
|
|
||||||
- Change normalize_path_middleware to use 308 redirect instead of 301.
|
|
||||||
|
|
||||||
This behavior should prevent clients from being unable to use PUT/POST
|
|
||||||
methods on endpoints that are redirected because of a trailing slash.
|
|
||||||
`#3579 <https://github.com/aio-libs/aiohttp/issues/3579>`_
|
|
||||||
- Drop the processed task from ``all_tasks()`` list early. It prevents logging about a
|
|
||||||
task with unhandled exception when the server is used in conjunction with
|
|
||||||
``asyncio.run()``. `#3587 <https://github.com/aio-libs/aiohttp/issues/3587>`_
|
|
||||||
- ``Signal`` type annotation changed from ``Signal[Callable[['TraceConfig'],
|
|
||||||
Awaitable[None]]]`` to ``Signal[Callable[ClientSession, SimpleNamespace, ...]``.
|
|
||||||
`#3595 <https://github.com/aio-libs/aiohttp/issues/3595>`_
|
|
||||||
- Use sanitized URL as Location header in redirects
|
|
||||||
`#3614 <https://github.com/aio-libs/aiohttp/issues/3614>`_
|
|
||||||
- Improve typing annotations for multipart.py along with changes required
|
|
||||||
by mypy in files that references multipart.py.
|
|
||||||
`#3621 <https://github.com/aio-libs/aiohttp/issues/3621>`_
|
|
||||||
- Close session created inside ``aiohttp.request`` when unhandled exception occurs
|
|
||||||
`#3628 <https://github.com/aio-libs/aiohttp/issues/3628>`_
|
|
||||||
- Cleanup per-chunk data in generic data read. Memory leak fixed.
|
|
||||||
`#3631 <https://github.com/aio-libs/aiohttp/issues/3631>`_
|
|
||||||
- Use correct type for add_view and family
|
|
||||||
`#3633 <https://github.com/aio-libs/aiohttp/issues/3633>`_
|
|
||||||
- Fix _keepalive field in __slots__ of ``RequestHandler``.
|
|
||||||
`#3644 <https://github.com/aio-libs/aiohttp/issues/3644>`_
|
|
||||||
- Properly handle ConnectionResetError, to silence the "Cannot write to closing
|
|
||||||
transport" exception when clients disconnect uncleanly.
|
|
||||||
`#3648 <https://github.com/aio-libs/aiohttp/issues/3648>`_
|
|
||||||
- Suppress pytest warnings due to ``test_utils`` classes
|
|
||||||
`#3660 <https://github.com/aio-libs/aiohttp/issues/3660>`_
|
|
||||||
- Fix overshadowing of overlapped sub-application prefixes.
|
|
||||||
`#3701 <https://github.com/aio-libs/aiohttp/issues/3701>`_
|
|
||||||
- Fixed return type annotation for WSMessage.json()
|
|
||||||
`#3720 <https://github.com/aio-libs/aiohttp/issues/3720>`_
|
|
||||||
- Properly expose TooManyRedirects publicly as documented.
|
|
||||||
`#3818 <https://github.com/aio-libs/aiohttp/issues/3818>`_
|
|
||||||
- Fix missing brackets for IPv6 in proxy CONNECT request
|
|
||||||
`#3841 <https://github.com/aio-libs/aiohttp/issues/3841>`_
|
|
||||||
- Make the signature of ``aiohttp.test_utils.TestClient.request`` match
|
|
||||||
``asyncio.ClientSession.request`` according to the docs `#3852
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3852>`_
|
|
||||||
- Use correct style for re-exported imports, makes mypy ``--strict`` mode happy.
|
|
||||||
`#3868 <https://github.com/aio-libs/aiohttp/issues/3868>`_
|
|
||||||
- Fixed type annotation for add_view method of UrlDispatcher to accept any subclass of
|
|
||||||
View `#3880 <https://github.com/aio-libs/aiohttp/issues/3880>`_
|
|
||||||
- Made cython HTTP parser set Reason-Phrase of the response to an empty string if it is
|
|
||||||
missing. `#3906 <https://github.com/aio-libs/aiohttp/issues/3906>`_
|
|
||||||
- Add URL to the string representation of ClientResponseError.
|
|
||||||
`#3959 <https://github.com/aio-libs/aiohttp/issues/3959>`_
|
|
||||||
- Accept ``istr`` keys in ``LooseHeaders`` type hints.
|
|
||||||
`#3976 <https://github.com/aio-libs/aiohttp/issues/3976>`_
|
|
||||||
- Fixed race conditions in _resolve_host caching and throttling when tracing is enabled.
|
|
||||||
`#4013 <https://github.com/aio-libs/aiohttp/issues/4013>`_
|
|
||||||
- For URLs like "unix://localhost/..." set Host HTTP header to "localhost" instead of
|
|
||||||
"localhost:None". `#4039 <https://github.com/aio-libs/aiohttp/issues/4039>`_
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Modify documentation for Background Tasks to remove deprecated usage of event loop.
|
|
||||||
`#3526 <https://github.com/aio-libs/aiohttp/issues/3526>`_
|
|
||||||
- use ``if __name__ == '__main__':`` in server examples.
|
|
||||||
`#3775 <https://github.com/aio-libs/aiohttp/issues/3775>`_
|
|
||||||
- Update documentation reference to the default access logger.
|
|
||||||
`#3783 <https://github.com/aio-libs/aiohttp/issues/3783>`_
|
|
||||||
- Improve documentation for ``web.BaseRequest.path`` and ``web.BaseRequest.raw_path``.
|
|
||||||
`#3791 <https://github.com/aio-libs/aiohttp/issues/3791>`_
|
|
||||||
- Removed deprecation warning in tracing example docs
|
|
||||||
`#3964 <https://github.com/aio-libs/aiohttp/issues/3964>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.5.4 (2019-01-12)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Fix stream ``.read()`` / ``.readany()`` / ``.iter_any()`` which used to return a
|
|
||||||
partial content only in case of compressed content
|
|
||||||
`#3525 <https://github.com/aio-libs/aiohttp/issues/3525>`_
|
|
||||||
|
|
||||||
|
|
||||||
3.5.3 (2019-01-10)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Fix type stubs for ``aiohttp.web.run_app(access_log=True)`` and fix edge case of
|
|
||||||
``access_log=True`` and the event loop being in debug mode. `#3504
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3504>`_
|
|
||||||
- Fix ``aiohttp.ClientTimeout`` type annotations to accept ``None`` for fields
|
|
||||||
`#3511 <https://github.com/aio-libs/aiohttp/issues/3511>`_
|
|
||||||
- Send custom per-request cookies even if session jar is empty
|
|
||||||
`#3515 <https://github.com/aio-libs/aiohttp/issues/3515>`_
|
|
||||||
- Restore Linux binary wheels publishing on PyPI
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.5.2 (2019-01-08)
|
|
||||||
==================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- ``FileResponse`` from ``web_fileresponse.py`` uses a ``ThreadPoolExecutor`` to work
|
|
||||||
with files asynchronously. I/O based payloads from ``payload.py`` uses a
|
|
||||||
``ThreadPoolExecutor`` to work with I/O objects asynchronously. `#3313
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3313>`_
|
|
||||||
- Internal Server Errors in plain text if the browser does not support HTML.
|
|
||||||
`#3483 <https://github.com/aio-libs/aiohttp/issues/3483>`_
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Preserve MultipartWriter parts headers on write. Refactor the way how
|
|
||||||
``Payload.headers`` are handled. Payload instances now always have headers and
|
|
||||||
Content-Type defined. Fix Payload Content-Disposition header reset after initial
|
|
||||||
creation. `#3035 <https://github.com/aio-libs/aiohttp/issues/3035>`_
|
|
||||||
- Log suppressed exceptions in ``GunicornWebWorker``.
|
|
||||||
`#3464 <https://github.com/aio-libs/aiohttp/issues/3464>`_
|
|
||||||
- Remove wildcard imports.
|
|
||||||
`#3468 <https://github.com/aio-libs/aiohttp/issues/3468>`_
|
|
||||||
- Use the same task for app initialization and web server handling in gunicorn workers.
|
|
||||||
It allows to use Python3.7 context vars smoothly.
|
|
||||||
`#3471 <https://github.com/aio-libs/aiohttp/issues/3471>`_
|
|
||||||
- Fix handling of chunked+gzipped response when first chunk does not give uncompressed
|
|
||||||
data `#3477 <https://github.com/aio-libs/aiohttp/issues/3477>`_
|
|
||||||
- Replace ``collections.MutableMapping`` with ``collections.abc.MutableMapping`` to
|
|
||||||
avoid a deprecation warning. `#3480
|
|
||||||
<https://github.com/aio-libs/aiohttp/issues/3480>`_
|
|
||||||
- ``Payload.size`` type annotation changed from ``Optional[float]`` to
|
|
||||||
``Optional[int]``. `#3484 <https://github.com/aio-libs/aiohttp/issues/3484>`_
|
|
||||||
- Ignore done tasks when cancels pending activities on ``web.run_app`` finalization.
|
|
||||||
`#3497 <https://github.com/aio-libs/aiohttp/issues/3497>`_
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Add documentation for ``aiohttp.web.HTTPException``.
|
|
||||||
`#3490 <https://github.com/aio-libs/aiohttp/issues/3490>`_
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- `#3487 <https://github.com/aio-libs/aiohttp/issues/3487>`_
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
3.5.1 (2018-12-24)
|
|
||||||
====================
|
|
||||||
|
|
||||||
- Fix a regression about ``ClientSession._requote_redirect_url`` modification in debug
|
|
||||||
mode.
|
|
||||||
|
|
||||||
3.5.0 (2018-12-22)
|
|
||||||
====================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- The library type annotations are checked in strict mode now.
|
|
||||||
- Add support for setting cookies for individual request (`#2387
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/2387>`_)
|
|
||||||
- Application.add_domain implementation (`#2809
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/2809>`_)
|
|
||||||
- The default ``app`` in the request returned by ``test_utils.make_mocked_request`` can
|
|
||||||
now have objects assigned to it and retrieved using the ``[]`` operator. (`#3174
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3174>`_)
|
|
||||||
- Make ``request.url`` accessible when transport is closed. (`#3177
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3177>`_)
|
|
||||||
- Add ``zlib_executor_size`` argument to ``Response`` constructor to allow compression
|
|
||||||
to run in a background executor to avoid blocking the main thread and potentially
|
|
||||||
triggering health check failures. (`#3205
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3205>`_)
|
|
||||||
- Enable users to set ``ClientTimeout`` in ``aiohttp.request`` (`#3213
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3213>`_)
|
|
||||||
- Don't raise a warning if ``NETRC`` environment variable is not set and ``~/.netrc``
|
|
||||||
file doesn't exist. (`#3267 <https://github.com/aio-libs/aiohttp/pull/3267>`_)
|
|
||||||
- Add default logging handler to web.run_app If the ``Application.debug``` flag is set
|
|
||||||
and the default logger ``aiohttp.access`` is used, access logs will now be output
|
|
||||||
using a *stderr* ``StreamHandler`` if no handlers are attached. Furthermore, if the
|
|
||||||
default logger has no log level set, the log level will be set to ``DEBUG``. (`#3324
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3324>`_)
|
|
||||||
- Add method argument to ``session.ws_connect()``. Sometimes server API requires a
|
|
||||||
different HTTP method for WebSocket connection establishment. For example, ``Docker
|
|
||||||
exec`` needs POST. (`#3378 <https://github.com/aio-libs/aiohttp/pull/3378>`_)
|
|
||||||
- Create a task per request handling. (`#3406
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3406>`_)
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Enable passing ``access_log_class`` via ``handler_args`` (`#3158
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3158>`_)
|
|
||||||
- Return empty bytes with end-of-chunk marker in empty stream reader. (`#3186
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3186>`_)
|
|
||||||
- Accept ``CIMultiDictProxy`` instances for ``headers`` argument in ``web.Response``
|
|
||||||
constructor. (`#3207 <https://github.com/aio-libs/aiohttp/pull/3207>`_)
|
|
||||||
- Don't uppercase HTTP method in parser (`#3233
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3233>`_)
|
|
||||||
- Make method match regexp RFC-7230 compliant (`#3235
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3235>`_)
|
|
||||||
- Add ``app.pre_frozen`` state to properly handle startup signals in
|
|
||||||
sub-applications. (`#3237 <https://github.com/aio-libs/aiohttp/pull/3237>`_)
|
|
||||||
- Enhanced parsing and validation of helpers.BasicAuth.decode. (`#3239
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3239>`_)
|
|
||||||
- Change imports from collections module in preparation for 3.8. (`#3258
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3258>`_)
|
|
||||||
- Ensure Host header is added first to ClientRequest to better replicate browser (`#3265
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3265>`_)
|
|
||||||
- Fix forward compatibility with Python 3.8: importing ABCs directly from the
|
|
||||||
collections module will not be supported anymore. (`#3273
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3273>`_)
|
|
||||||
- Keep the query string by ``normalize_path_middleware``. (`#3278
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3278>`_)
|
|
||||||
- Fix missing parameter ``raise_for_status`` for aiohttp.request() (`#3290
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3290>`_)
|
|
||||||
- Bracket IPv6 addresses in the HOST header (`#3304
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3304>`_)
|
|
||||||
- Fix default message for server ping and pong frames. (`#3308
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3308>`_)
|
|
||||||
- Fix tests/test_connector.py typo and tests/autobahn/server.py duplicate loop
|
|
||||||
def. (`#3337 <https://github.com/aio-libs/aiohttp/pull/3337>`_)
|
|
||||||
- Fix false-negative indicator end_of_HTTP_chunk in StreamReader.readchunk function
|
|
||||||
(`#3361 <https://github.com/aio-libs/aiohttp/pull/3361>`_)
|
|
||||||
- Release HTTP response before raising status exception (`#3364
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3364>`_)
|
|
||||||
- Fix task cancellation when ``sendfile()`` syscall is used by static file
|
|
||||||
handling. (`#3383 <https://github.com/aio-libs/aiohttp/pull/3383>`_)
|
|
||||||
- Fix stack trace for ``asyncio.TimeoutError`` which was not logged, when it is caught
|
|
||||||
in the handler. (`#3414 <https://github.com/aio-libs/aiohttp/pull/3414>`_)
|
|
||||||
|
|
||||||
|
|
||||||
Improved Documentation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
- Improve documentation of ``Application.make_handler`` parameters. (`#3152
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3152>`_)
|
|
||||||
- Fix BaseRequest.raw_headers doc. (`#3215
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3215>`_)
|
|
||||||
- Fix typo in TypeError exception reason in ``web.Application._handle`` (`#3229
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3229>`_)
|
|
||||||
- Make server access log format placeholder %b documentation reflect
|
|
||||||
behavior and docstring. (`#3307 <https://github.com/aio-libs/aiohttp/pull/3307>`_)
|
|
||||||
|
|
||||||
|
|
||||||
Deprecations and Removals
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
- Deprecate modification of ``session.requote_redirect_url`` (`#2278
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/2278>`_)
|
|
||||||
- Deprecate ``stream.unread_data()`` (`#3260
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3260>`_)
|
|
||||||
- Deprecated use of boolean in ``resp.enable_compression()`` (`#3318
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3318>`_)
|
|
||||||
- Encourage creation of aiohttp public objects inside a coroutine (`#3331
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3331>`_)
|
|
||||||
- Drop dead ``Connection.detach()`` and ``Connection.writer``. Both methods were broken
|
|
||||||
for more than 2 years. (`#3358 <https://github.com/aio-libs/aiohttp/pull/3358>`_)
|
|
||||||
- Deprecate ``app.loop``, ``request.loop``, ``client.loop`` and ``connector.loop``
|
|
||||||
properties. (`#3374 <https://github.com/aio-libs/aiohttp/pull/3374>`_)
|
|
||||||
- Deprecate explicit debug argument. Use asyncio debug mode instead. (`#3381
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3381>`_)
|
|
||||||
- Deprecate body parameter in HTTPException (and derived classes) constructor. (`#3385
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3385>`_)
|
|
||||||
- Deprecate bare connector close, use ``async with connector:`` and ``await
|
|
||||||
connector.close()`` instead. (`#3417
|
|
||||||
<https://github.com/aio-libs/aiohttp/pull/3417>`_)
|
|
||||||
- Deprecate obsolete ``read_timeout`` and ``conn_timeout`` in ``ClientSession``
|
|
||||||
constructor. (`#3438 <https://github.com/aio-libs/aiohttp/pull/3438>`_)
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- #3341, #3351
|
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
aiohttp/__init__.py,sha256=kz1O7HepmZpNXAVHUS8h8tDqHip6G7ApF45_04MurZ0,6934
|
|
||||||
aiohttp/_cparser.pxd,sha256=tgw30SL6kQSczzGMlMhx2Cuhf_O8P8ZPimVCb85xILc,3959
|
|
||||||
aiohttp/_find_header.c,sha256=s8Urnxv8CDd2NuKCEzCsLWy4NbVFXfLMNlg-s2bCg3A,187570
|
|
||||||
aiohttp/_find_header.h,sha256=5oOgQ85nF6V7rpU8NhyE5vyGkTo1Cgf1GIYrtxSTzQI,170
|
|
||||||
aiohttp/_find_header.pxd,sha256=0GfwFCPN2zxEKTO1_MA5sYq2UfzsG8kcV3aTqvwlz3g,68
|
|
||||||
aiohttp/_frozenlist.c,sha256=4m7L0I0wWumfGoXIMaYYeKxf9zG3ssoIxB-meEQDgh4,294193
|
|
||||||
aiohttp/_frozenlist.cpython-37m-darwin.so,sha256=R4a4ORdHkSdM-c61yAaUMUplWjGVtJDFX4JIRAsf7iw,92472
|
|
||||||
aiohttp/_frozenlist.pyx,sha256=BD8LcERExsWdo4qzuuQ84f-L_pHVzkUQO0lEAOe3Fog,2605
|
|
||||||
aiohttp/_headers.pxi,sha256=n701k28dVPjwRnx5j6LpJhLTfj7dqu2vJt7f0O60Oyg,2007
|
|
||||||
aiohttp/_helpers.c,sha256=meXX95gKMrawWPUl-q3Sdyf4VxSdsH-8lbCON6-ATOo,211989
|
|
||||||
aiohttp/_helpers.cpython-37m-darwin.so,sha256=XyeNlPexhFMQrbjG30DWOffQ6FyYBa3QblpW0kwopX0,68128
|
|
||||||
aiohttp/_helpers.pyi,sha256=ZoKiJSS51PxELhI2cmIr5737YjjZcJt7FbIRO3ym1Ss,202
|
|
||||||
aiohttp/_helpers.pyx,sha256=XeLbNft5X_4ifi8QB8i6TyrRuayijMSO3IDHeSA89uM,1049
|
|
||||||
aiohttp/_http_parser.c,sha256=O51TiHCn0eXUeiBWCyl7E7y-JjZLgeLRj10D18HudSU,1011524
|
|
||||||
aiohttp/_http_parser.cpython-37m-darwin.so,sha256=fxc3YCdZ9YM_1M_ke621_TlAh0d4FPkhgzofgAsX3Ds,497200
|
|
||||||
aiohttp/_http_parser.pyx,sha256=8GiPsugeqSvwoXgiJg2VkaMJeRAdoSpLhzET_EWftfo,29022
|
|
||||||
aiohttp/_http_writer.c,sha256=QxJ09pt3guD8aVsQ7LaAxX4vxBUhJ7RhxeVMVWu8BUk,213020
|
|
||||||
aiohttp/_http_writer.cpython-37m-darwin.so,sha256=KW_V9a5eTCniI-P4eMxmqGI14cUseIR9ld_YRYAbpOE,62872
|
|
||||||
aiohttp/_http_writer.pyx,sha256=Tnt_e6pcZZVOhaW3yNt3hqDsNJgIGwqUIPeSqAMIYoE,4200
|
|
||||||
aiohttp/_websocket.c,sha256=F5V1i__yewPzFcgYKmcTWk_CjptCZUYlhQesX14u4ec,137427
|
|
||||||
aiohttp/_websocket.cpython-37m-darwin.so,sha256=90xJg0yXVPna02tLlSXA4cwzMCs2Hts6WvEcrYKFtWU,43648
|
|
||||||
aiohttp/_websocket.pyx,sha256=1XuOSNDCbyDrzF5uMA2isqausSs8l2jWTLDlNDLM9Io,1561
|
|
||||||
aiohttp/abc.py,sha256=tbhrXunYB8-Jmq3-e6zbYtXaCR7Vfb8eRo4Ifk-juLQ,5247
|
|
||||||
aiohttp/base_protocol.py,sha256=eNgmzKP-Lbh5vxSmShDBKTJ0JSAVpcrAZa-EKqmCTKA,2701
|
|
||||||
aiohttp/client.py,sha256=9firS0XBuNEGshPE81CsmLxnkfkZoJsRUV6amoGZ1FA,43916
|
|
||||||
aiohttp/client_exceptions.py,sha256=fW2rrDEcWitwpXhQIFtViuC2JEHDx1oIXXQsj6YGd5I,8529
|
|
||||||
aiohttp/client_proto.py,sha256=Iek_ZuTr_2xFoKt30zveOBh1xYt6iXE7XZVwUPiCXTs,8163
|
|
||||||
aiohttp/client_reqrep.py,sha256=okBWp3TC_leAiD0-kEtS8H0v8fZPTSVzyDXDwusSZrs,36439
|
|
||||||
aiohttp/client_ws.py,sha256=sxkvG5Vw37Mr6V_8w9WZ0ulo_jKL_tRtSK1l9bRIPlY,10287
|
|
||||||
aiohttp/connector.py,sha256=9A8wGKGoOXZCzRUBnw2dwT457aWXAxFSn4fPaEK5Lh8,42968
|
|
||||||
aiohttp/cookiejar.py,sha256=SbX_n44bl69PrGIPOmj2imHwynzBlUbxrZUe16T3K-4,12163
|
|
||||||
aiohttp/formdata.py,sha256=UJ4DgqX4Jk-LZpnKOeOCUewL5264eO4DyHPKOk5vgos,6080
|
|
||||||
aiohttp/frozenlist.py,sha256=nFqoDVmyxfStqiY_FxcrME8GpvhRDXf3mh-BGCVxhnQ,1718
|
|
||||||
aiohttp/frozenlist.pyi,sha256=bRNKoI2j1roPdtgfx_nseDanvBqZsZUNHDqmXtfjlRo,1434
|
|
||||||
aiohttp/hdrs.py,sha256=WsjDJYADYEyJk7-oNXNhA2M3MwtyLkhJAklyzLtclfU,3446
|
|
||||||
aiohttp/helpers.py,sha256=iAQNQGGoqN8ovgdMfHC2rggYSbOaSTToiUJqg1LJrzw,22918
|
|
||||||
aiohttp/http.py,sha256=CHdBKhli7yITl_VfpBDdjgF6nT_cCWPRXov8UzBeo2k,1824
|
|
||||||
aiohttp/http_exceptions.py,sha256=eACQt7azwNbUi8aqXB5J2tIcc_CB4_4y4mLM9SZd4tM,2586
|
|
||||||
aiohttp/http_parser.py,sha256=wNwFkglxrBFRUHWN4AUbbUuFckMgwy5k_cX6zR4CEiE,30781
|
|
||||||
aiohttp/http_websocket.py,sha256=k_mvpvQUsliv7F7ZlXmz01hzBnoysSHAr4KyZDC-O78,25098
|
|
||||||
aiohttp/http_writer.py,sha256=ofDAEH8IJry013XsbrrfNiV82Fr7Gs3d7syduMx1vYU,5341
|
|
||||||
aiohttp/locks.py,sha256=j5VO5HkQY_N5DRJAezRJXavE9y2N9Y9Sq3oWu8ftAOA,1220
|
|
||||||
aiohttp/log.py,sha256=BbNKx9e3VMIm0xYjZI0IcBBoS7wjdeIeSaiJE7-qK2g,325
|
|
||||||
aiohttp/multipart.py,sha256=ZrChyWL811TpsxcL0rcmx3VIJjzj-cINkrjgk9GhBt4,32251
|
|
||||||
aiohttp/payload.py,sha256=jvvuncmzh4RuHUR05srhzSMXCUCxclUPgFnEDm1vl40,13333
|
|
||||||
aiohttp/payload_streamer.py,sha256=qW8jvfRJYYnpRro74vQbrTXFd_5JZsHyVGi4-bulOwo,2102
|
|
||||||
aiohttp/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
|
|
||||||
aiohttp/pytest_plugin.py,sha256=0z5FACR9mUEZp72jakz_jfDuy-dsmv2HISIY0EB01K4,11009
|
|
||||||
aiohttp/resolver.py,sha256=VmUmtY_mjuRQCy4OrByS74bgooPzUeHrO_pEUPaMXc4,4608
|
|
||||||
aiohttp/signals.py,sha256=YSWNpNheFirmccC7vWMr8J1TDWBN_P0m3Ch_FiZ3HVA,852
|
|
||||||
aiohttp/signals.pyi,sha256=SLTfUPdx1-g4VSTqCnBXyhSCl0-KQ-Z0mCsEsIvBfV4,319
|
|
||||||
aiohttp/streams.py,sha256=zmCpGESe27u2b-JmyOem_FThgOn9Iq_TpwIHJMjJfmY,20530
|
|
||||||
aiohttp/tcp_helpers.py,sha256=l3XD9wWiuMawXCMdqiNSRHIz0iVtMKi7lwqXnHvyK3k,962
|
|
||||||
aiohttp/test_utils.py,sha256=NG7YcgAE_E7LOlcaNQ8JhgOadhg5bg174-tukUQLDMA,20253
|
|
||||||
aiohttp/tracing.py,sha256=-G2cfF6LNFqY8nCbnHJ8NkLdnJV1WSjRtKOnJ1gz_lw,14363
|
|
||||||
aiohttp/typedefs.py,sha256=PUsJ7Y-f8bRqyDOdqd681VvaNi_LETRJ5L7rCPfPRN4,1374
|
|
||||||
aiohttp/web.py,sha256=WNOEIih5I3g_0BSIJkrKvWuTQ8qZVKoGkjg-v6wcSms,17881
|
|
||||||
aiohttp/web_app.py,sha256=lDfoKwy1y8_pnMkrqcfHiMnhWwH_4FCkCuSMLT4huA0,17053
|
|
||||||
aiohttp/web_exceptions.py,sha256=-ZqN_zv7EiaLDS0Na5IpgXmiiNv9QZm8sebYoW3zeYc,10106
|
|
||||||
aiohttp/web_fileresponse.py,sha256=hpzBp-L-VuVrlsSjoTvGRWpgWZrLODkaoQlGCNQrpag,9025
|
|
||||||
aiohttp/web_log.py,sha256=hDwnH_USBiNqC1-XggEorcuVQUuKhEezCfjPY1wkac4,7498
|
|
||||||
aiohttp/web_middlewares.py,sha256=QYD2PiB_AMkIQWpBEiQRP0UM_vBmZ7sAM9Qes3SoXH0,4193
|
|
||||||
aiohttp/web_protocol.py,sha256=2nHNxxg7Mf8ZP88SFPxnBNGL8zmPhQpjtZ68VT0PeIo,23251
|
|
||||||
aiohttp/web_request.py,sha256=fY-GmGuV3oayr5r0Szd1VsHraN8T-uW2wCij6iFfZOg,26454
|
|
||||||
aiohttp/web_response.py,sha256=Xb9PfJhFHzu9Od4qHRmN9O6z5ojBRuL0ETqmR6S_8Ek,26202
|
|
||||||
aiohttp/web_routedef.py,sha256=PIHf5KvaOnG30pg1J8IhZewoHLOEMd5emWBZBBWS9yE,6109
|
|
||||||
aiohttp/web_runner.py,sha256=4AM4y6lgmCxBU1AHt1Sdd7IpB4pm9dWqSdapNfgzqG8,11194
|
|
||||||
aiohttp/web_server.py,sha256=Zp-StXkTSDZ8Lyg2joDdRD05QRu9oh8LET3AKfx9D2I,2058
|
|
||||||
aiohttp/web_urldispatcher.py,sha256=M930z-C2umyGyU4KmGVsabJNL5ZpT6Aj5tpSf6mSSPc,39532
|
|
||||||
aiohttp/web_ws.py,sha256=f912oImpu973icUK0QUjeXIxa57R837gdkUUn1ZnSpQ,16783
|
|
||||||
aiohttp/worker.py,sha256=T1CvPYNP4rqsdpyz3dNRuqNF5bzrH3WaEdDMLDsfLOc,8022
|
|
||||||
aiohttp/.hash/_cparser.pxd.hash,sha256=g9ydcacNvFizu1GVl--RrXpWlSawIZ-fN050i7iUo5c,64
|
|
||||||
aiohttp/.hash/_find_header.pxd.hash,sha256=BtbSPeZ2eOtt807jecTkoEeBQ4C5AiBxjzbvJj9eV6E,64
|
|
||||||
aiohttp/.hash/_frozenlist.pyx.hash,sha256=hmgKZnSvKFUnUuT9uCV7orAxYP6Js40SN70NrDWjDmI,64
|
|
||||||
aiohttp/.hash/_helpers.pyi.hash,sha256=pvlsQIMFVemm4rvS1BkQkfS2PGgJn5ZO2RmMSFxwCiE,64
|
|
||||||
aiohttp/.hash/_helpers.pyx.hash,sha256=JWBqptE_5nbmOzG3dYXdmEuAy_Y2snJfcR124T4TZpo,64
|
|
||||||
aiohttp/.hash/_http_parser.pyx.hash,sha256=qP5I4Mu5pXc_3aDx2RSPfN2j1akQ-szA5D269nw_Sxw,64
|
|
||||||
aiohttp/.hash/_http_writer.pyx.hash,sha256=CFBO14jclGTimiln6e8yXnfWwk-4s_ylld3lE6JrnY8,64
|
|
||||||
aiohttp/.hash/_websocket.pyx.hash,sha256=_6cqIcIQjICfniPJShOdspzQaB95Lu6cinOZJ6yESz8,64
|
|
||||||
aiohttp/.hash/frozenlist.pyi.hash,sha256=021hwHbk-0EfQ2c5mefxSctfrrvJ7CnQ0mPXYTBGJ4o,64
|
|
||||||
aiohttp/.hash/hdrs.py.hash,sha256=T8i1rURdD81PR6t5qOA05Lg3yV6C9Mm4LTjJucc36yA,64
|
|
||||||
aiohttp/.hash/signals.pyi.hash,sha256=GQA2kFtF7PaBpEJDZ6OpBpFaMhi_2ieNF7HaxYDmKQQ,64
|
|
||||||
aiohttp-3.7.4.dist-info/LICENSE.txt,sha256=lmJ77QrQjpsu-p9OBOgIN80FUOdpSg_sM7HaslUCgqs,11321
|
|
||||||
aiohttp-3.7.4.dist-info/METADATA,sha256=0Dssbrhzakprcv1OgK090WQ7AsehillLeEr7pAK0wOg,38601
|
|
||||||
aiohttp-3.7.4.dist-info/WHEEL,sha256=SPFsdN-g7Osut7El6pRHojcroYU7REc1DdD4VXzi4B4,111
|
|
||||||
aiohttp-3.7.4.dist-info/top_level.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8
|
|
||||||
aiohttp-3.7.4.dist-info/RECORD,,
|
|
||||||
aiohttp-3.7.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
aiohttp/__pycache__/web_urldispatcher.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/payload.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/typedefs.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_ws.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_routedef.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/client_exceptions.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/client.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_exceptions.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/test_utils.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/streams.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/client_proto.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_app.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/signals.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_fileresponse.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_protocol.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/multipart.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/payload_streamer.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/connector.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_runner.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/log.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/hdrs.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/client_ws.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/pytest_plugin.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_request.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_server.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/http_parser.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/base_protocol.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/abc.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_log.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/formdata.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/cookiejar.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/http.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/tcp_helpers.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_response.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/http_writer.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/helpers.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/resolver.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/http_exceptions.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/frozenlist.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/tracing.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/client_reqrep.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/worker.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/http_websocket.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/web_middlewares.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/__init__.cpython-37.pyc,,
|
|
||||||
aiohttp/__pycache__/locks.cpython-37.pyc,,
|
|
|
@ -1,5 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: false
|
|
||||||
Tag: cp37-cp37m-macosx_10_14_x86_64
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
aiohttp
|
|
|
@ -1 +0,0 @@
|
||||||
b60c37d122fa91049ccf318c94c871d82ba17ff3bc3fc64f8a65426fce7120b7
|
|
|
@ -1 +0,0 @@
|
||||||
d067f01423cddb3c442933b5fcc039b18ab651fcec1bc91c577693aafc25cf78
|
|
|
@ -1 +0,0 @@
|
||||||
043f0b704444c6c59da38ab3bae43ce1ff8bfe91d5ce45103b494400e7b71688
|
|
|
@ -1 +0,0 @@
|
||||||
6682a22524b9d4fc442e123672622be7bdfb6238d9709b7b15b2113b7ca6d52b
|
|
|
@ -1 +0,0 @@
|
||||||
5de2db35fb795ffe227e2f1007c8ba4f2ad1b9aca28cc48edc80c779203cf6e3
|
|
|
@ -1 +0,0 @@
|
||||||
f0688fb2e81ea92bf0a17822260d9591a30979101da12a4b873113fc459fb5fa
|
|
|
@ -1 +0,0 @@
|
||||||
4e7b7f7baa5c65954e85a5b7c8db7786a0ec3498081b0a9420f792a803086281
|
|
|
@ -1 +0,0 @@
|
||||||
d57b8e48d0c26f20ebcc5e6e300da2b2a6aeb12b3c9768d64cb0e53432ccf48a
|
|
|
@ -1 +0,0 @@
|
||||||
6d134aa08da3d6ba0f76d81fc7f9ec7836a7bc1a99b1950d1c3aa65ed7e3951a
|
|
|
@ -1 +0,0 @@
|
||||||
5ac8c3258003604c8993bfa8357361036337330b722e4849024972ccbb5c95f5
|
|
|
@ -1 +0,0 @@
|
||||||
48b4df50f771d7e8385524ea0a7057ca1482974f8a43e674982b04b08bc17d5e
|
|
|
@ -1,217 +0,0 @@
|
||||||
__version__ = "3.7.4"
|
|
||||||
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
from . import hdrs as hdrs
|
|
||||||
from .client import (
|
|
||||||
BaseConnector as BaseConnector,
|
|
||||||
ClientConnectionError as ClientConnectionError,
|
|
||||||
ClientConnectorCertificateError as ClientConnectorCertificateError,
|
|
||||||
ClientConnectorError as ClientConnectorError,
|
|
||||||
ClientConnectorSSLError as ClientConnectorSSLError,
|
|
||||||
ClientError as ClientError,
|
|
||||||
ClientHttpProxyError as ClientHttpProxyError,
|
|
||||||
ClientOSError as ClientOSError,
|
|
||||||
ClientPayloadError as ClientPayloadError,
|
|
||||||
ClientProxyConnectionError as ClientProxyConnectionError,
|
|
||||||
ClientRequest as ClientRequest,
|
|
||||||
ClientResponse as ClientResponse,
|
|
||||||
ClientResponseError as ClientResponseError,
|
|
||||||
ClientSession as ClientSession,
|
|
||||||
ClientSSLError as ClientSSLError,
|
|
||||||
ClientTimeout as ClientTimeout,
|
|
||||||
ClientWebSocketResponse as ClientWebSocketResponse,
|
|
||||||
ContentTypeError as ContentTypeError,
|
|
||||||
Fingerprint as Fingerprint,
|
|
||||||
InvalidURL as InvalidURL,
|
|
||||||
NamedPipeConnector as NamedPipeConnector,
|
|
||||||
RequestInfo as RequestInfo,
|
|
||||||
ServerConnectionError as ServerConnectionError,
|
|
||||||
ServerDisconnectedError as ServerDisconnectedError,
|
|
||||||
ServerFingerprintMismatch as ServerFingerprintMismatch,
|
|
||||||
ServerTimeoutError as ServerTimeoutError,
|
|
||||||
TCPConnector as TCPConnector,
|
|
||||||
TooManyRedirects as TooManyRedirects,
|
|
||||||
UnixConnector as UnixConnector,
|
|
||||||
WSServerHandshakeError as WSServerHandshakeError,
|
|
||||||
request as request,
|
|
||||||
)
|
|
||||||
from .cookiejar import CookieJar as CookieJar, DummyCookieJar as DummyCookieJar
|
|
||||||
from .formdata import FormData as FormData
|
|
||||||
from .helpers import BasicAuth as BasicAuth, ChainMapProxy as ChainMapProxy
|
|
||||||
from .http import (
|
|
||||||
HttpVersion as HttpVersion,
|
|
||||||
HttpVersion10 as HttpVersion10,
|
|
||||||
HttpVersion11 as HttpVersion11,
|
|
||||||
WebSocketError as WebSocketError,
|
|
||||||
WSCloseCode as WSCloseCode,
|
|
||||||
WSMessage as WSMessage,
|
|
||||||
WSMsgType as WSMsgType,
|
|
||||||
)
|
|
||||||
from .multipart import (
|
|
||||||
BadContentDispositionHeader as BadContentDispositionHeader,
|
|
||||||
BadContentDispositionParam as BadContentDispositionParam,
|
|
||||||
BodyPartReader as BodyPartReader,
|
|
||||||
MultipartReader as MultipartReader,
|
|
||||||
MultipartWriter as MultipartWriter,
|
|
||||||
content_disposition_filename as content_disposition_filename,
|
|
||||||
parse_content_disposition as parse_content_disposition,
|
|
||||||
)
|
|
||||||
from .payload import (
|
|
||||||
PAYLOAD_REGISTRY as PAYLOAD_REGISTRY,
|
|
||||||
AsyncIterablePayload as AsyncIterablePayload,
|
|
||||||
BufferedReaderPayload as BufferedReaderPayload,
|
|
||||||
BytesIOPayload as BytesIOPayload,
|
|
||||||
BytesPayload as BytesPayload,
|
|
||||||
IOBasePayload as IOBasePayload,
|
|
||||||
JsonPayload as JsonPayload,
|
|
||||||
Payload as Payload,
|
|
||||||
StringIOPayload as StringIOPayload,
|
|
||||||
StringPayload as StringPayload,
|
|
||||||
TextIOPayload as TextIOPayload,
|
|
||||||
get_payload as get_payload,
|
|
||||||
payload_type as payload_type,
|
|
||||||
)
|
|
||||||
from .payload_streamer import streamer as streamer
|
|
||||||
from .resolver import (
|
|
||||||
AsyncResolver as AsyncResolver,
|
|
||||||
DefaultResolver as DefaultResolver,
|
|
||||||
ThreadedResolver as ThreadedResolver,
|
|
||||||
)
|
|
||||||
from .signals import Signal as Signal
|
|
||||||
from .streams import (
|
|
||||||
EMPTY_PAYLOAD as EMPTY_PAYLOAD,
|
|
||||||
DataQueue as DataQueue,
|
|
||||||
EofStream as EofStream,
|
|
||||||
FlowControlDataQueue as FlowControlDataQueue,
|
|
||||||
StreamReader as StreamReader,
|
|
||||||
)
|
|
||||||
from .tracing import (
|
|
||||||
TraceConfig as TraceConfig,
|
|
||||||
TraceConnectionCreateEndParams as TraceConnectionCreateEndParams,
|
|
||||||
TraceConnectionCreateStartParams as TraceConnectionCreateStartParams,
|
|
||||||
TraceConnectionQueuedEndParams as TraceConnectionQueuedEndParams,
|
|
||||||
TraceConnectionQueuedStartParams as TraceConnectionQueuedStartParams,
|
|
||||||
TraceConnectionReuseconnParams as TraceConnectionReuseconnParams,
|
|
||||||
TraceDnsCacheHitParams as TraceDnsCacheHitParams,
|
|
||||||
TraceDnsCacheMissParams as TraceDnsCacheMissParams,
|
|
||||||
TraceDnsResolveHostEndParams as TraceDnsResolveHostEndParams,
|
|
||||||
TraceDnsResolveHostStartParams as TraceDnsResolveHostStartParams,
|
|
||||||
TraceRequestChunkSentParams as TraceRequestChunkSentParams,
|
|
||||||
TraceRequestEndParams as TraceRequestEndParams,
|
|
||||||
TraceRequestExceptionParams as TraceRequestExceptionParams,
|
|
||||||
TraceRequestRedirectParams as TraceRequestRedirectParams,
|
|
||||||
TraceRequestStartParams as TraceRequestStartParams,
|
|
||||||
TraceResponseChunkReceivedParams as TraceResponseChunkReceivedParams,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__: Tuple[str, ...] = (
|
|
||||||
"hdrs",
|
|
||||||
# client
|
|
||||||
"BaseConnector",
|
|
||||||
"ClientConnectionError",
|
|
||||||
"ClientConnectorCertificateError",
|
|
||||||
"ClientConnectorError",
|
|
||||||
"ClientConnectorSSLError",
|
|
||||||
"ClientError",
|
|
||||||
"ClientHttpProxyError",
|
|
||||||
"ClientOSError",
|
|
||||||
"ClientPayloadError",
|
|
||||||
"ClientProxyConnectionError",
|
|
||||||
"ClientResponse",
|
|
||||||
"ClientRequest",
|
|
||||||
"ClientResponseError",
|
|
||||||
"ClientSSLError",
|
|
||||||
"ClientSession",
|
|
||||||
"ClientTimeout",
|
|
||||||
"ClientWebSocketResponse",
|
|
||||||
"ContentTypeError",
|
|
||||||
"Fingerprint",
|
|
||||||
"InvalidURL",
|
|
||||||
"RequestInfo",
|
|
||||||
"ServerConnectionError",
|
|
||||||
"ServerDisconnectedError",
|
|
||||||
"ServerFingerprintMismatch",
|
|
||||||
"ServerTimeoutError",
|
|
||||||
"TCPConnector",
|
|
||||||
"TooManyRedirects",
|
|
||||||
"UnixConnector",
|
|
||||||
"NamedPipeConnector",
|
|
||||||
"WSServerHandshakeError",
|
|
||||||
"request",
|
|
||||||
# cookiejar
|
|
||||||
"CookieJar",
|
|
||||||
"DummyCookieJar",
|
|
||||||
# formdata
|
|
||||||
"FormData",
|
|
||||||
# helpers
|
|
||||||
"BasicAuth",
|
|
||||||
"ChainMapProxy",
|
|
||||||
# http
|
|
||||||
"HttpVersion",
|
|
||||||
"HttpVersion10",
|
|
||||||
"HttpVersion11",
|
|
||||||
"WSMsgType",
|
|
||||||
"WSCloseCode",
|
|
||||||
"WSMessage",
|
|
||||||
"WebSocketError",
|
|
||||||
# multipart
|
|
||||||
"BadContentDispositionHeader",
|
|
||||||
"BadContentDispositionParam",
|
|
||||||
"BodyPartReader",
|
|
||||||
"MultipartReader",
|
|
||||||
"MultipartWriter",
|
|
||||||
"content_disposition_filename",
|
|
||||||
"parse_content_disposition",
|
|
||||||
# payload
|
|
||||||
"AsyncIterablePayload",
|
|
||||||
"BufferedReaderPayload",
|
|
||||||
"BytesIOPayload",
|
|
||||||
"BytesPayload",
|
|
||||||
"IOBasePayload",
|
|
||||||
"JsonPayload",
|
|
||||||
"PAYLOAD_REGISTRY",
|
|
||||||
"Payload",
|
|
||||||
"StringIOPayload",
|
|
||||||
"StringPayload",
|
|
||||||
"TextIOPayload",
|
|
||||||
"get_payload",
|
|
||||||
"payload_type",
|
|
||||||
# payload_streamer
|
|
||||||
"streamer",
|
|
||||||
# resolver
|
|
||||||
"AsyncResolver",
|
|
||||||
"DefaultResolver",
|
|
||||||
"ThreadedResolver",
|
|
||||||
# signals
|
|
||||||
"Signal",
|
|
||||||
"DataQueue",
|
|
||||||
"EMPTY_PAYLOAD",
|
|
||||||
"EofStream",
|
|
||||||
"FlowControlDataQueue",
|
|
||||||
"StreamReader",
|
|
||||||
# tracing
|
|
||||||
"TraceConfig",
|
|
||||||
"TraceConnectionCreateEndParams",
|
|
||||||
"TraceConnectionCreateStartParams",
|
|
||||||
"TraceConnectionQueuedEndParams",
|
|
||||||
"TraceConnectionQueuedStartParams",
|
|
||||||
"TraceConnectionReuseconnParams",
|
|
||||||
"TraceDnsCacheHitParams",
|
|
||||||
"TraceDnsCacheMissParams",
|
|
||||||
"TraceDnsResolveHostEndParams",
|
|
||||||
"TraceDnsResolveHostStartParams",
|
|
||||||
"TraceRequestChunkSentParams",
|
|
||||||
"TraceRequestEndParams",
|
|
||||||
"TraceRequestExceptionParams",
|
|
||||||
"TraceRequestRedirectParams",
|
|
||||||
"TraceRequestStartParams",
|
|
||||||
"TraceResponseChunkReceivedParams",
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from .worker import GunicornUVLoopWebWorker, GunicornWebWorker
|
|
||||||
|
|
||||||
__all__ += ("GunicornWebWorker", "GunicornUVLoopWebWorker")
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
pass
|
|
|
@ -1,140 +0,0 @@
|
||||||
from libc.stdint cimport uint16_t, uint32_t, uint64_t
|
|
||||||
|
|
||||||
|
|
||||||
cdef extern from "../vendor/http-parser/http_parser.h":
|
|
||||||
ctypedef int (*http_data_cb) (http_parser*,
|
|
||||||
const char *at,
|
|
||||||
size_t length) except -1
|
|
||||||
|
|
||||||
ctypedef int (*http_cb) (http_parser*) except -1
|
|
||||||
|
|
||||||
struct http_parser:
|
|
||||||
unsigned int type
|
|
||||||
unsigned int flags
|
|
||||||
unsigned int state
|
|
||||||
unsigned int header_state
|
|
||||||
unsigned int index
|
|
||||||
|
|
||||||
uint32_t nread
|
|
||||||
uint64_t content_length
|
|
||||||
|
|
||||||
unsigned short http_major
|
|
||||||
unsigned short http_minor
|
|
||||||
unsigned int status_code
|
|
||||||
unsigned int method
|
|
||||||
unsigned int http_errno
|
|
||||||
|
|
||||||
unsigned int upgrade
|
|
||||||
|
|
||||||
void *data
|
|
||||||
|
|
||||||
struct http_parser_settings:
|
|
||||||
http_cb on_message_begin
|
|
||||||
http_data_cb on_url
|
|
||||||
http_data_cb on_status
|
|
||||||
http_data_cb on_header_field
|
|
||||||
http_data_cb on_header_value
|
|
||||||
http_cb on_headers_complete
|
|
||||||
http_data_cb on_body
|
|
||||||
http_cb on_message_complete
|
|
||||||
http_cb on_chunk_header
|
|
||||||
http_cb on_chunk_complete
|
|
||||||
|
|
||||||
enum http_parser_type:
|
|
||||||
HTTP_REQUEST,
|
|
||||||
HTTP_RESPONSE,
|
|
||||||
HTTP_BOTH
|
|
||||||
|
|
||||||
enum http_errno:
|
|
||||||
HPE_OK,
|
|
||||||
HPE_CB_message_begin,
|
|
||||||
HPE_CB_url,
|
|
||||||
HPE_CB_header_field,
|
|
||||||
HPE_CB_header_value,
|
|
||||||
HPE_CB_headers_complete,
|
|
||||||
HPE_CB_body,
|
|
||||||
HPE_CB_message_complete,
|
|
||||||
HPE_CB_status,
|
|
||||||
HPE_CB_chunk_header,
|
|
||||||
HPE_CB_chunk_complete,
|
|
||||||
HPE_INVALID_EOF_STATE,
|
|
||||||
HPE_HEADER_OVERFLOW,
|
|
||||||
HPE_CLOSED_CONNECTION,
|
|
||||||
HPE_INVALID_VERSION,
|
|
||||||
HPE_INVALID_STATUS,
|
|
||||||
HPE_INVALID_METHOD,
|
|
||||||
HPE_INVALID_URL,
|
|
||||||
HPE_INVALID_HOST,
|
|
||||||
HPE_INVALID_PORT,
|
|
||||||
HPE_INVALID_PATH,
|
|
||||||
HPE_INVALID_QUERY_STRING,
|
|
||||||
HPE_INVALID_FRAGMENT,
|
|
||||||
HPE_LF_EXPECTED,
|
|
||||||
HPE_INVALID_HEADER_TOKEN,
|
|
||||||
HPE_INVALID_CONTENT_LENGTH,
|
|
||||||
HPE_INVALID_CHUNK_SIZE,
|
|
||||||
HPE_INVALID_CONSTANT,
|
|
||||||
HPE_INVALID_INTERNAL_STATE,
|
|
||||||
HPE_STRICT,
|
|
||||||
HPE_PAUSED,
|
|
||||||
HPE_UNKNOWN
|
|
||||||
|
|
||||||
enum flags:
|
|
||||||
F_CHUNKED,
|
|
||||||
F_CONNECTION_KEEP_ALIVE,
|
|
||||||
F_CONNECTION_CLOSE,
|
|
||||||
F_CONNECTION_UPGRADE,
|
|
||||||
F_TRAILING,
|
|
||||||
F_UPGRADE,
|
|
||||||
F_SKIPBODY,
|
|
||||||
F_CONTENTLENGTH
|
|
||||||
|
|
||||||
enum http_method:
|
|
||||||
DELETE, GET, HEAD, POST, PUT, CONNECT, OPTIONS, TRACE, COPY,
|
|
||||||
LOCK, MKCOL, MOVE, PROPFIND, PROPPATCH, SEARCH, UNLOCK, BIND,
|
|
||||||
REBIND, UNBIND, ACL, REPORT, MKACTIVITY, CHECKOUT, MERGE,
|
|
||||||
MSEARCH, NOTIFY, SUBSCRIBE, UNSUBSCRIBE, PATCH, PURGE, MKCALENDAR,
|
|
||||||
LINK, UNLINK
|
|
||||||
|
|
||||||
void http_parser_init(http_parser *parser, http_parser_type type)
|
|
||||||
|
|
||||||
size_t http_parser_execute(http_parser *parser,
|
|
||||||
const http_parser_settings *settings,
|
|
||||||
const char *data,
|
|
||||||
size_t len)
|
|
||||||
|
|
||||||
int http_should_keep_alive(const http_parser *parser)
|
|
||||||
|
|
||||||
void http_parser_settings_init(http_parser_settings *settings)
|
|
||||||
|
|
||||||
const char *http_errno_name(http_errno err)
|
|
||||||
const char *http_errno_description(http_errno err)
|
|
||||||
const char *http_method_str(http_method m)
|
|
||||||
|
|
||||||
# URL Parser
|
|
||||||
|
|
||||||
enum http_parser_url_fields:
|
|
||||||
UF_SCHEMA = 0,
|
|
||||||
UF_HOST = 1,
|
|
||||||
UF_PORT = 2,
|
|
||||||
UF_PATH = 3,
|
|
||||||
UF_QUERY = 4,
|
|
||||||
UF_FRAGMENT = 5,
|
|
||||||
UF_USERINFO = 6,
|
|
||||||
UF_MAX = 7
|
|
||||||
|
|
||||||
struct http_parser_url_field_data:
|
|
||||||
uint16_t off
|
|
||||||
uint16_t len
|
|
||||||
|
|
||||||
struct http_parser_url:
|
|
||||||
uint16_t field_set
|
|
||||||
uint16_t port
|
|
||||||
http_parser_url_field_data[<int>UF_MAX] field_data
|
|
||||||
|
|
||||||
void http_parser_url_init(http_parser_url *u)
|
|
||||||
|
|
||||||
int http_parser_parse_url(const char *buf,
|
|
||||||
size_t buflen,
|
|
||||||
int is_connect,
|
|
||||||
http_parser_url *u)
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +0,0 @@
|
||||||
#ifndef _FIND_HEADERS_H
|
|
||||||
#define _FIND_HEADERS_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int find_header(const char *str, int size);
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
|
@ -1,2 +0,0 @@
|
||||||
cdef extern from "_find_header.h":
|
|
||||||
int find_header(char *, int)
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -1,108 +0,0 @@
|
||||||
from collections.abc import MutableSequence
|
|
||||||
|
|
||||||
|
|
||||||
cdef class FrozenList:
|
|
||||||
|
|
||||||
cdef readonly bint frozen
|
|
||||||
cdef list _items
|
|
||||||
|
|
||||||
def __init__(self, items=None):
|
|
||||||
self.frozen = False
|
|
||||||
if items is not None:
|
|
||||||
items = list(items)
|
|
||||||
else:
|
|
||||||
items = []
|
|
||||||
self._items = items
|
|
||||||
|
|
||||||
cdef object _check_frozen(self):
|
|
||||||
if self.frozen:
|
|
||||||
raise RuntimeError("Cannot modify frozen list.")
|
|
||||||
|
|
||||||
cdef inline object _fast_len(self):
|
|
||||||
return len(self._items)
|
|
||||||
|
|
||||||
def freeze(self):
|
|
||||||
self.frozen = True
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
|
||||||
return self._items[index]
|
|
||||||
|
|
||||||
def __setitem__(self, index, value):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items[index] = value
|
|
||||||
|
|
||||||
def __delitem__(self, index):
|
|
||||||
self._check_frozen()
|
|
||||||
del self._items[index]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self._fast_len()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self._items.__iter__()
|
|
||||||
|
|
||||||
def __reversed__(self):
|
|
||||||
return self._items.__reversed__()
|
|
||||||
|
|
||||||
def __richcmp__(self, other, op):
|
|
||||||
if op == 0: # <
|
|
||||||
return list(self) < other
|
|
||||||
if op == 1: # <=
|
|
||||||
return list(self) <= other
|
|
||||||
if op == 2: # ==
|
|
||||||
return list(self) == other
|
|
||||||
if op == 3: # !=
|
|
||||||
return list(self) != other
|
|
||||||
if op == 4: # >
|
|
||||||
return list(self) > other
|
|
||||||
if op == 5: # =>
|
|
||||||
return list(self) >= other
|
|
||||||
|
|
||||||
def insert(self, pos, item):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items.insert(pos, item)
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
return item in self._items
|
|
||||||
|
|
||||||
def __iadd__(self, items):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items += list(items)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def index(self, item):
|
|
||||||
return self._items.index(item)
|
|
||||||
|
|
||||||
def remove(self, item):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items.remove(item)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items.clear()
|
|
||||||
|
|
||||||
def extend(self, items):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items += list(items)
|
|
||||||
|
|
||||||
def reverse(self):
|
|
||||||
self._check_frozen()
|
|
||||||
self._items.reverse()
|
|
||||||
|
|
||||||
def pop(self, index=-1):
|
|
||||||
self._check_frozen()
|
|
||||||
return self._items.pop(index)
|
|
||||||
|
|
||||||
def append(self, item):
|
|
||||||
self._check_frozen()
|
|
||||||
return self._items.append(item)
|
|
||||||
|
|
||||||
def count(self, item):
|
|
||||||
return self._items.count(item)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<FrozenList(frozen={}, {!r})>'.format(self.frozen,
|
|
||||||
self._items)
|
|
||||||
|
|
||||||
|
|
||||||
MutableSequence.register(FrozenList)
|
|
|
@ -1,83 +0,0 @@
|
||||||
# The file is autogenerated from aiohttp/hdrs.py
|
|
||||||
# Run ./tools/gen.py to update it after the origin changing.
|
|
||||||
|
|
||||||
from . import hdrs
|
|
||||||
cdef tuple headers = (
|
|
||||||
hdrs.ACCEPT,
|
|
||||||
hdrs.ACCEPT_CHARSET,
|
|
||||||
hdrs.ACCEPT_ENCODING,
|
|
||||||
hdrs.ACCEPT_LANGUAGE,
|
|
||||||
hdrs.ACCEPT_RANGES,
|
|
||||||
hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
|
|
||||||
hdrs.ACCESS_CONTROL_ALLOW_HEADERS,
|
|
||||||
hdrs.ACCESS_CONTROL_ALLOW_METHODS,
|
|
||||||
hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
||||||
hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
||||||
hdrs.ACCESS_CONTROL_MAX_AGE,
|
|
||||||
hdrs.ACCESS_CONTROL_REQUEST_HEADERS,
|
|
||||||
hdrs.ACCESS_CONTROL_REQUEST_METHOD,
|
|
||||||
hdrs.AGE,
|
|
||||||
hdrs.ALLOW,
|
|
||||||
hdrs.AUTHORIZATION,
|
|
||||||
hdrs.CACHE_CONTROL,
|
|
||||||
hdrs.CONNECTION,
|
|
||||||
hdrs.CONTENT_DISPOSITION,
|
|
||||||
hdrs.CONTENT_ENCODING,
|
|
||||||
hdrs.CONTENT_LANGUAGE,
|
|
||||||
hdrs.CONTENT_LENGTH,
|
|
||||||
hdrs.CONTENT_LOCATION,
|
|
||||||
hdrs.CONTENT_MD5,
|
|
||||||
hdrs.CONTENT_RANGE,
|
|
||||||
hdrs.CONTENT_TRANSFER_ENCODING,
|
|
||||||
hdrs.CONTENT_TYPE,
|
|
||||||
hdrs.COOKIE,
|
|
||||||
hdrs.DATE,
|
|
||||||
hdrs.DESTINATION,
|
|
||||||
hdrs.DIGEST,
|
|
||||||
hdrs.ETAG,
|
|
||||||
hdrs.EXPECT,
|
|
||||||
hdrs.EXPIRES,
|
|
||||||
hdrs.FORWARDED,
|
|
||||||
hdrs.FROM,
|
|
||||||
hdrs.HOST,
|
|
||||||
hdrs.IF_MATCH,
|
|
||||||
hdrs.IF_MODIFIED_SINCE,
|
|
||||||
hdrs.IF_NONE_MATCH,
|
|
||||||
hdrs.IF_RANGE,
|
|
||||||
hdrs.IF_UNMODIFIED_SINCE,
|
|
||||||
hdrs.KEEP_ALIVE,
|
|
||||||
hdrs.LAST_EVENT_ID,
|
|
||||||
hdrs.LAST_MODIFIED,
|
|
||||||
hdrs.LINK,
|
|
||||||
hdrs.LOCATION,
|
|
||||||
hdrs.MAX_FORWARDS,
|
|
||||||
hdrs.ORIGIN,
|
|
||||||
hdrs.PRAGMA,
|
|
||||||
hdrs.PROXY_AUTHENTICATE,
|
|
||||||
hdrs.PROXY_AUTHORIZATION,
|
|
||||||
hdrs.RANGE,
|
|
||||||
hdrs.REFERER,
|
|
||||||
hdrs.RETRY_AFTER,
|
|
||||||
hdrs.SEC_WEBSOCKET_ACCEPT,
|
|
||||||
hdrs.SEC_WEBSOCKET_EXTENSIONS,
|
|
||||||
hdrs.SEC_WEBSOCKET_KEY,
|
|
||||||
hdrs.SEC_WEBSOCKET_KEY1,
|
|
||||||
hdrs.SEC_WEBSOCKET_PROTOCOL,
|
|
||||||
hdrs.SEC_WEBSOCKET_VERSION,
|
|
||||||
hdrs.SERVER,
|
|
||||||
hdrs.SET_COOKIE,
|
|
||||||
hdrs.TE,
|
|
||||||
hdrs.TRAILER,
|
|
||||||
hdrs.TRANSFER_ENCODING,
|
|
||||||
hdrs.URI,
|
|
||||||
hdrs.UPGRADE,
|
|
||||||
hdrs.USER_AGENT,
|
|
||||||
hdrs.VARY,
|
|
||||||
hdrs.VIA,
|
|
||||||
hdrs.WWW_AUTHENTICATE,
|
|
||||||
hdrs.WANT_DIGEST,
|
|
||||||
hdrs.WARNING,
|
|
||||||
hdrs.X_FORWARDED_FOR,
|
|
||||||
hdrs.X_FORWARDED_HOST,
|
|
||||||
hdrs.X_FORWARDED_PROTO,
|
|
||||||
)
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -1,6 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
class reify:
|
|
||||||
def __init__(self, wrapped: Any) -> None: ...
|
|
||||||
def __get__(self, inst: Any, owner: Any) -> Any: ...
|
|
||||||
def __set__(self, inst: Any, value: Any) -> None: ...
|
|
|
@ -1,35 +0,0 @@
|
||||||
cdef class reify:
|
|
||||||
"""Use as a class method decorator. It operates almost exactly like
|
|
||||||
the Python `@property` decorator, but it puts the result of the
|
|
||||||
method it decorates into the instance dict after the first call,
|
|
||||||
effectively replacing the function it decorates with an instance
|
|
||||||
variable. It is, in Python parlance, a data descriptor.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
cdef object wrapped
|
|
||||||
cdef object name
|
|
||||||
|
|
||||||
def __init__(self, wrapped):
|
|
||||||
self.wrapped = wrapped
|
|
||||||
self.name = wrapped.__name__
|
|
||||||
|
|
||||||
@property
|
|
||||||
def __doc__(self):
|
|
||||||
return self.wrapped.__doc__
|
|
||||||
|
|
||||||
def __get__(self, inst, owner):
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
return inst._cache[self.name]
|
|
||||||
except KeyError:
|
|
||||||
val = self.wrapped(inst)
|
|
||||||
inst._cache[self.name] = val
|
|
||||||
return val
|
|
||||||
except AttributeError:
|
|
||||||
if inst is None:
|
|
||||||
return self
|
|
||||||
raise
|
|
||||||
|
|
||||||
def __set__(self, inst, value):
|
|
||||||
raise AttributeError("reified property is read-only")
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -1,875 +0,0 @@
|
||||||
#cython: language_level=3
|
|
||||||
#
|
|
||||||
# Based on https://github.com/MagicStack/httptools
|
|
||||||
#
|
|
||||||
from __future__ import absolute_import, print_function
|
|
||||||
|
|
||||||
from cpython cimport (
|
|
||||||
Py_buffer,
|
|
||||||
PyBUF_SIMPLE,
|
|
||||||
PyBuffer_Release,
|
|
||||||
PyBytes_AsString,
|
|
||||||
PyBytes_AsStringAndSize,
|
|
||||||
PyObject_GetBuffer,
|
|
||||||
)
|
|
||||||
from cpython.mem cimport PyMem_Free, PyMem_Malloc
|
|
||||||
from libc.limits cimport ULLONG_MAX
|
|
||||||
from libc.string cimport memcpy
|
|
||||||
|
|
||||||
from multidict import CIMultiDict as _CIMultiDict, CIMultiDictProxy as _CIMultiDictProxy
|
|
||||||
from yarl import URL as _URL
|
|
||||||
|
|
||||||
from aiohttp import hdrs
|
|
||||||
|
|
||||||
from .http_exceptions import (
|
|
||||||
BadHttpMessage,
|
|
||||||
BadStatusLine,
|
|
||||||
ContentLengthError,
|
|
||||||
InvalidHeader,
|
|
||||||
InvalidURLError,
|
|
||||||
LineTooLong,
|
|
||||||
PayloadEncodingError,
|
|
||||||
TransferEncodingError,
|
|
||||||
)
|
|
||||||
from .http_parser import DeflateBuffer as _DeflateBuffer
|
|
||||||
from .http_writer import (
|
|
||||||
HttpVersion as _HttpVersion,
|
|
||||||
HttpVersion10 as _HttpVersion10,
|
|
||||||
HttpVersion11 as _HttpVersion11,
|
|
||||||
)
|
|
||||||
from .streams import EMPTY_PAYLOAD as _EMPTY_PAYLOAD, StreamReader as _StreamReader
|
|
||||||
|
|
||||||
cimport cython
|
|
||||||
|
|
||||||
from aiohttp cimport _cparser as cparser
|
|
||||||
|
|
||||||
include "_headers.pxi"
|
|
||||||
|
|
||||||
from aiohttp cimport _find_header
|
|
||||||
|
|
||||||
DEF DEFAULT_FREELIST_SIZE = 250
|
|
||||||
|
|
||||||
cdef extern from "Python.h":
|
|
||||||
int PyByteArray_Resize(object, Py_ssize_t) except -1
|
|
||||||
Py_ssize_t PyByteArray_Size(object) except -1
|
|
||||||
char* PyByteArray_AsString(object)
|
|
||||||
|
|
||||||
__all__ = ('HttpRequestParser', 'HttpResponseParser',
|
|
||||||
'RawRequestMessage', 'RawResponseMessage')
|
|
||||||
|
|
||||||
cdef object URL = _URL
|
|
||||||
cdef object URL_build = URL.build
|
|
||||||
cdef object CIMultiDict = _CIMultiDict
|
|
||||||
cdef object CIMultiDictProxy = _CIMultiDictProxy
|
|
||||||
cdef object HttpVersion = _HttpVersion
|
|
||||||
cdef object HttpVersion10 = _HttpVersion10
|
|
||||||
cdef object HttpVersion11 = _HttpVersion11
|
|
||||||
cdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1
|
|
||||||
cdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING
|
|
||||||
cdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD
|
|
||||||
cdef object StreamReader = _StreamReader
|
|
||||||
cdef object DeflateBuffer = _DeflateBuffer
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline object extend(object buf, const char* at, size_t length):
|
|
||||||
cdef Py_ssize_t s
|
|
||||||
cdef char* ptr
|
|
||||||
s = PyByteArray_Size(buf)
|
|
||||||
PyByteArray_Resize(buf, s + length)
|
|
||||||
ptr = PyByteArray_AsString(buf)
|
|
||||||
memcpy(ptr + s, at, length)
|
|
||||||
|
|
||||||
|
|
||||||
DEF METHODS_COUNT = 34;
|
|
||||||
|
|
||||||
cdef list _http_method = []
|
|
||||||
|
|
||||||
for i in range(METHODS_COUNT):
|
|
||||||
_http_method.append(
|
|
||||||
cparser.http_method_str(<cparser.http_method> i).decode('ascii'))
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline str http_method_str(int i):
|
|
||||||
if i < METHODS_COUNT:
|
|
||||||
return <str>_http_method[i]
|
|
||||||
else:
|
|
||||||
return "<unknown>"
|
|
||||||
|
|
||||||
cdef inline object find_header(bytes raw_header):
|
|
||||||
cdef Py_ssize_t size
|
|
||||||
cdef char *buf
|
|
||||||
cdef int idx
|
|
||||||
PyBytes_AsStringAndSize(raw_header, &buf, &size)
|
|
||||||
idx = _find_header.find_header(buf, size)
|
|
||||||
if idx == -1:
|
|
||||||
return raw_header.decode('utf-8', 'surrogateescape')
|
|
||||||
return headers[idx]
|
|
||||||
|
|
||||||
|
|
||||||
@cython.freelist(DEFAULT_FREELIST_SIZE)
|
|
||||||
cdef class RawRequestMessage:
|
|
||||||
cdef readonly str method
|
|
||||||
cdef readonly str path
|
|
||||||
cdef readonly object version # HttpVersion
|
|
||||||
cdef readonly object headers # CIMultiDict
|
|
||||||
cdef readonly object raw_headers # tuple
|
|
||||||
cdef readonly object should_close
|
|
||||||
cdef readonly object compression
|
|
||||||
cdef readonly object upgrade
|
|
||||||
cdef readonly object chunked
|
|
||||||
cdef readonly object url # yarl.URL
|
|
||||||
|
|
||||||
def __init__(self, method, path, version, headers, raw_headers,
|
|
||||||
should_close, compression, upgrade, chunked, url):
|
|
||||||
self.method = method
|
|
||||||
self.path = path
|
|
||||||
self.version = version
|
|
||||||
self.headers = headers
|
|
||||||
self.raw_headers = raw_headers
|
|
||||||
self.should_close = should_close
|
|
||||||
self.compression = compression
|
|
||||||
self.upgrade = upgrade
|
|
||||||
self.chunked = chunked
|
|
||||||
self.url = url
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = []
|
|
||||||
info.append(("method", self.method))
|
|
||||||
info.append(("path", self.path))
|
|
||||||
info.append(("version", self.version))
|
|
||||||
info.append(("headers", self.headers))
|
|
||||||
info.append(("raw_headers", self.raw_headers))
|
|
||||||
info.append(("should_close", self.should_close))
|
|
||||||
info.append(("compression", self.compression))
|
|
||||||
info.append(("upgrade", self.upgrade))
|
|
||||||
info.append(("chunked", self.chunked))
|
|
||||||
info.append(("url", self.url))
|
|
||||||
sinfo = ', '.join(name + '=' + repr(val) for name, val in info)
|
|
||||||
return '<RawRequestMessage(' + sinfo + ')>'
|
|
||||||
|
|
||||||
def _replace(self, **dct):
|
|
||||||
cdef RawRequestMessage ret
|
|
||||||
ret = _new_request_message(self.method,
|
|
||||||
self.path,
|
|
||||||
self.version,
|
|
||||||
self.headers,
|
|
||||||
self.raw_headers,
|
|
||||||
self.should_close,
|
|
||||||
self.compression,
|
|
||||||
self.upgrade,
|
|
||||||
self.chunked,
|
|
||||||
self.url)
|
|
||||||
if "method" in dct:
|
|
||||||
ret.method = dct["method"]
|
|
||||||
if "path" in dct:
|
|
||||||
ret.path = dct["path"]
|
|
||||||
if "version" in dct:
|
|
||||||
ret.version = dct["version"]
|
|
||||||
if "headers" in dct:
|
|
||||||
ret.headers = dct["headers"]
|
|
||||||
if "raw_headers" in dct:
|
|
||||||
ret.raw_headers = dct["raw_headers"]
|
|
||||||
if "should_close" in dct:
|
|
||||||
ret.should_close = dct["should_close"]
|
|
||||||
if "compression" in dct:
|
|
||||||
ret.compression = dct["compression"]
|
|
||||||
if "upgrade" in dct:
|
|
||||||
ret.upgrade = dct["upgrade"]
|
|
||||||
if "chunked" in dct:
|
|
||||||
ret.chunked = dct["chunked"]
|
|
||||||
if "url" in dct:
|
|
||||||
ret.url = dct["url"]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
cdef _new_request_message(str method,
|
|
||||||
str path,
|
|
||||||
object version,
|
|
||||||
object headers,
|
|
||||||
object raw_headers,
|
|
||||||
bint should_close,
|
|
||||||
object compression,
|
|
||||||
bint upgrade,
|
|
||||||
bint chunked,
|
|
||||||
object url):
|
|
||||||
cdef RawRequestMessage ret
|
|
||||||
ret = RawRequestMessage.__new__(RawRequestMessage)
|
|
||||||
ret.method = method
|
|
||||||
ret.path = path
|
|
||||||
ret.version = version
|
|
||||||
ret.headers = headers
|
|
||||||
ret.raw_headers = raw_headers
|
|
||||||
ret.should_close = should_close
|
|
||||||
ret.compression = compression
|
|
||||||
ret.upgrade = upgrade
|
|
||||||
ret.chunked = chunked
|
|
||||||
ret.url = url
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@cython.freelist(DEFAULT_FREELIST_SIZE)
|
|
||||||
cdef class RawResponseMessage:
|
|
||||||
cdef readonly object version # HttpVersion
|
|
||||||
cdef readonly int code
|
|
||||||
cdef readonly str reason
|
|
||||||
cdef readonly object headers # CIMultiDict
|
|
||||||
cdef readonly object raw_headers # tuple
|
|
||||||
cdef readonly object should_close
|
|
||||||
cdef readonly object compression
|
|
||||||
cdef readonly object upgrade
|
|
||||||
cdef readonly object chunked
|
|
||||||
|
|
||||||
def __init__(self, version, code, reason, headers, raw_headers,
|
|
||||||
should_close, compression, upgrade, chunked):
|
|
||||||
self.version = version
|
|
||||||
self.code = code
|
|
||||||
self.reason = reason
|
|
||||||
self.headers = headers
|
|
||||||
self.raw_headers = raw_headers
|
|
||||||
self.should_close = should_close
|
|
||||||
self.compression = compression
|
|
||||||
self.upgrade = upgrade
|
|
||||||
self.chunked = chunked
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = []
|
|
||||||
info.append(("version", self.version))
|
|
||||||
info.append(("code", self.code))
|
|
||||||
info.append(("reason", self.reason))
|
|
||||||
info.append(("headers", self.headers))
|
|
||||||
info.append(("raw_headers", self.raw_headers))
|
|
||||||
info.append(("should_close", self.should_close))
|
|
||||||
info.append(("compression", self.compression))
|
|
||||||
info.append(("upgrade", self.upgrade))
|
|
||||||
info.append(("chunked", self.chunked))
|
|
||||||
sinfo = ', '.join(name + '=' + repr(val) for name, val in info)
|
|
||||||
return '<RawResponseMessage(' + sinfo + ')>'
|
|
||||||
|
|
||||||
|
|
||||||
cdef _new_response_message(object version,
|
|
||||||
int code,
|
|
||||||
str reason,
|
|
||||||
object headers,
|
|
||||||
object raw_headers,
|
|
||||||
bint should_close,
|
|
||||||
object compression,
|
|
||||||
bint upgrade,
|
|
||||||
bint chunked):
|
|
||||||
cdef RawResponseMessage ret
|
|
||||||
ret = RawResponseMessage.__new__(RawResponseMessage)
|
|
||||||
ret.version = version
|
|
||||||
ret.code = code
|
|
||||||
ret.reason = reason
|
|
||||||
ret.headers = headers
|
|
||||||
ret.raw_headers = raw_headers
|
|
||||||
ret.should_close = should_close
|
|
||||||
ret.compression = compression
|
|
||||||
ret.upgrade = upgrade
|
|
||||||
ret.chunked = chunked
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@cython.internal
|
|
||||||
cdef class HttpParser:
|
|
||||||
|
|
||||||
cdef:
|
|
||||||
cparser.http_parser* _cparser
|
|
||||||
cparser.http_parser_settings* _csettings
|
|
||||||
|
|
||||||
bytearray _raw_name
|
|
||||||
bytearray _raw_value
|
|
||||||
bint _has_value
|
|
||||||
|
|
||||||
object _protocol
|
|
||||||
object _loop
|
|
||||||
object _timer
|
|
||||||
|
|
||||||
size_t _max_line_size
|
|
||||||
size_t _max_field_size
|
|
||||||
size_t _max_headers
|
|
||||||
bint _response_with_body
|
|
||||||
bint _read_until_eof
|
|
||||||
|
|
||||||
bint _started
|
|
||||||
object _url
|
|
||||||
bytearray _buf
|
|
||||||
str _path
|
|
||||||
str _reason
|
|
||||||
object _headers
|
|
||||||
list _raw_headers
|
|
||||||
bint _upgraded
|
|
||||||
list _messages
|
|
||||||
object _payload
|
|
||||||
bint _payload_error
|
|
||||||
object _payload_exception
|
|
||||||
object _last_error
|
|
||||||
bint _auto_decompress
|
|
||||||
int _limit
|
|
||||||
|
|
||||||
str _content_encoding
|
|
||||||
|
|
||||||
Py_buffer py_buf
|
|
||||||
|
|
||||||
def __cinit__(self):
|
|
||||||
self._cparser = <cparser.http_parser*> \
|
|
||||||
PyMem_Malloc(sizeof(cparser.http_parser))
|
|
||||||
if self._cparser is NULL:
|
|
||||||
raise MemoryError()
|
|
||||||
|
|
||||||
self._csettings = <cparser.http_parser_settings*> \
|
|
||||||
PyMem_Malloc(sizeof(cparser.http_parser_settings))
|
|
||||||
if self._csettings is NULL:
|
|
||||||
raise MemoryError()
|
|
||||||
|
|
||||||
def __dealloc__(self):
|
|
||||||
PyMem_Free(self._cparser)
|
|
||||||
PyMem_Free(self._csettings)
|
|
||||||
|
|
||||||
cdef _init(self, cparser.http_parser_type mode,
|
|
||||||
object protocol, object loop, int limit,
|
|
||||||
object timer=None,
|
|
||||||
size_t max_line_size=8190, size_t max_headers=32768,
|
|
||||||
size_t max_field_size=8190, payload_exception=None,
|
|
||||||
bint response_with_body=True, bint read_until_eof=False,
|
|
||||||
bint auto_decompress=True):
|
|
||||||
cparser.http_parser_init(self._cparser, mode)
|
|
||||||
self._cparser.data = <void*>self
|
|
||||||
self._cparser.content_length = 0
|
|
||||||
|
|
||||||
cparser.http_parser_settings_init(self._csettings)
|
|
||||||
|
|
||||||
self._protocol = protocol
|
|
||||||
self._loop = loop
|
|
||||||
self._timer = timer
|
|
||||||
|
|
||||||
self._buf = bytearray()
|
|
||||||
self._payload = None
|
|
||||||
self._payload_error = 0
|
|
||||||
self._payload_exception = payload_exception
|
|
||||||
self._messages = []
|
|
||||||
|
|
||||||
self._raw_name = bytearray()
|
|
||||||
self._raw_value = bytearray()
|
|
||||||
self._has_value = False
|
|
||||||
|
|
||||||
self._max_line_size = max_line_size
|
|
||||||
self._max_headers = max_headers
|
|
||||||
self._max_field_size = max_field_size
|
|
||||||
self._response_with_body = response_with_body
|
|
||||||
self._read_until_eof = read_until_eof
|
|
||||||
self._upgraded = False
|
|
||||||
self._auto_decompress = auto_decompress
|
|
||||||
self._content_encoding = None
|
|
||||||
|
|
||||||
self._csettings.on_url = cb_on_url
|
|
||||||
self._csettings.on_status = cb_on_status
|
|
||||||
self._csettings.on_header_field = cb_on_header_field
|
|
||||||
self._csettings.on_header_value = cb_on_header_value
|
|
||||||
self._csettings.on_headers_complete = cb_on_headers_complete
|
|
||||||
self._csettings.on_body = cb_on_body
|
|
||||||
self._csettings.on_message_begin = cb_on_message_begin
|
|
||||||
self._csettings.on_message_complete = cb_on_message_complete
|
|
||||||
self._csettings.on_chunk_header = cb_on_chunk_header
|
|
||||||
self._csettings.on_chunk_complete = cb_on_chunk_complete
|
|
||||||
|
|
||||||
self._last_error = None
|
|
||||||
self._limit = limit
|
|
||||||
|
|
||||||
cdef _process_header(self):
|
|
||||||
if self._raw_name:
|
|
||||||
raw_name = bytes(self._raw_name)
|
|
||||||
raw_value = bytes(self._raw_value)
|
|
||||||
|
|
||||||
name = find_header(raw_name)
|
|
||||||
value = raw_value.decode('utf-8', 'surrogateescape')
|
|
||||||
|
|
||||||
self._headers.add(name, value)
|
|
||||||
|
|
||||||
if name is CONTENT_ENCODING:
|
|
||||||
self._content_encoding = value
|
|
||||||
|
|
||||||
PyByteArray_Resize(self._raw_name, 0)
|
|
||||||
PyByteArray_Resize(self._raw_value, 0)
|
|
||||||
self._has_value = False
|
|
||||||
self._raw_headers.append((raw_name, raw_value))
|
|
||||||
|
|
||||||
cdef _on_header_field(self, char* at, size_t length):
|
|
||||||
cdef Py_ssize_t size
|
|
||||||
cdef char *buf
|
|
||||||
if self._has_value:
|
|
||||||
self._process_header()
|
|
||||||
|
|
||||||
size = PyByteArray_Size(self._raw_name)
|
|
||||||
PyByteArray_Resize(self._raw_name, size + length)
|
|
||||||
buf = PyByteArray_AsString(self._raw_name)
|
|
||||||
memcpy(buf + size, at, length)
|
|
||||||
|
|
||||||
cdef _on_header_value(self, char* at, size_t length):
|
|
||||||
cdef Py_ssize_t size
|
|
||||||
cdef char *buf
|
|
||||||
|
|
||||||
size = PyByteArray_Size(self._raw_value)
|
|
||||||
PyByteArray_Resize(self._raw_value, size + length)
|
|
||||||
buf = PyByteArray_AsString(self._raw_value)
|
|
||||||
memcpy(buf + size, at, length)
|
|
||||||
self._has_value = True
|
|
||||||
|
|
||||||
cdef _on_headers_complete(self):
|
|
||||||
self._process_header()
|
|
||||||
|
|
||||||
method = http_method_str(self._cparser.method)
|
|
||||||
should_close = not cparser.http_should_keep_alive(self._cparser)
|
|
||||||
upgrade = self._cparser.upgrade
|
|
||||||
chunked = self._cparser.flags & cparser.F_CHUNKED
|
|
||||||
|
|
||||||
raw_headers = tuple(self._raw_headers)
|
|
||||||
headers = CIMultiDictProxy(self._headers)
|
|
||||||
|
|
||||||
if upgrade or self._cparser.method == 5: # cparser.CONNECT:
|
|
||||||
self._upgraded = True
|
|
||||||
|
|
||||||
# do not support old websocket spec
|
|
||||||
if SEC_WEBSOCKET_KEY1 in headers:
|
|
||||||
raise InvalidHeader(SEC_WEBSOCKET_KEY1)
|
|
||||||
|
|
||||||
encoding = None
|
|
||||||
enc = self._content_encoding
|
|
||||||
if enc is not None:
|
|
||||||
self._content_encoding = None
|
|
||||||
enc = enc.lower()
|
|
||||||
if enc in ('gzip', 'deflate', 'br'):
|
|
||||||
encoding = enc
|
|
||||||
|
|
||||||
if self._cparser.type == cparser.HTTP_REQUEST:
|
|
||||||
msg = _new_request_message(
|
|
||||||
method, self._path,
|
|
||||||
self.http_version(), headers, raw_headers,
|
|
||||||
should_close, encoding, upgrade, chunked, self._url)
|
|
||||||
else:
|
|
||||||
msg = _new_response_message(
|
|
||||||
self.http_version(), self._cparser.status_code, self._reason,
|
|
||||||
headers, raw_headers, should_close, encoding,
|
|
||||||
upgrade, chunked)
|
|
||||||
|
|
||||||
if (ULLONG_MAX > self._cparser.content_length > 0 or chunked or
|
|
||||||
self._cparser.method == 5 or # CONNECT: 5
|
|
||||||
(self._cparser.status_code >= 199 and
|
|
||||||
self._cparser.content_length == ULLONG_MAX and
|
|
||||||
self._read_until_eof)
|
|
||||||
):
|
|
||||||
payload = StreamReader(
|
|
||||||
self._protocol, timer=self._timer, loop=self._loop,
|
|
||||||
limit=self._limit)
|
|
||||||
else:
|
|
||||||
payload = EMPTY_PAYLOAD
|
|
||||||
|
|
||||||
self._payload = payload
|
|
||||||
if encoding is not None and self._auto_decompress:
|
|
||||||
self._payload = DeflateBuffer(payload, encoding)
|
|
||||||
|
|
||||||
if not self._response_with_body:
|
|
||||||
payload = EMPTY_PAYLOAD
|
|
||||||
|
|
||||||
self._messages.append((msg, payload))
|
|
||||||
|
|
||||||
cdef _on_message_complete(self):
|
|
||||||
self._payload.feed_eof()
|
|
||||||
self._payload = None
|
|
||||||
|
|
||||||
cdef _on_chunk_header(self):
|
|
||||||
self._payload.begin_http_chunk_receiving()
|
|
||||||
|
|
||||||
cdef _on_chunk_complete(self):
|
|
||||||
self._payload.end_http_chunk_receiving()
|
|
||||||
|
|
||||||
cdef object _on_status_complete(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
cdef inline http_version(self):
|
|
||||||
cdef cparser.http_parser* parser = self._cparser
|
|
||||||
|
|
||||||
if parser.http_major == 1:
|
|
||||||
if parser.http_minor == 0:
|
|
||||||
return HttpVersion10
|
|
||||||
elif parser.http_minor == 1:
|
|
||||||
return HttpVersion11
|
|
||||||
|
|
||||||
return HttpVersion(parser.http_major, parser.http_minor)
|
|
||||||
|
|
||||||
### Public API ###
|
|
||||||
|
|
||||||
def feed_eof(self):
|
|
||||||
cdef bytes desc
|
|
||||||
|
|
||||||
if self._payload is not None:
|
|
||||||
if self._cparser.flags & cparser.F_CHUNKED:
|
|
||||||
raise TransferEncodingError(
|
|
||||||
"Not enough data for satisfy transfer length header.")
|
|
||||||
elif self._cparser.flags & cparser.F_CONTENTLENGTH:
|
|
||||||
raise ContentLengthError(
|
|
||||||
"Not enough data for satisfy content length header.")
|
|
||||||
elif self._cparser.http_errno != cparser.HPE_OK:
|
|
||||||
desc = cparser.http_errno_description(
|
|
||||||
<cparser.http_errno> self._cparser.http_errno)
|
|
||||||
raise PayloadEncodingError(desc.decode('latin-1'))
|
|
||||||
else:
|
|
||||||
self._payload.feed_eof()
|
|
||||||
elif self._started:
|
|
||||||
self._on_headers_complete()
|
|
||||||
if self._messages:
|
|
||||||
return self._messages[-1][0]
|
|
||||||
|
|
||||||
def feed_data(self, data):
|
|
||||||
cdef:
|
|
||||||
size_t data_len
|
|
||||||
size_t nb
|
|
||||||
|
|
||||||
PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE)
|
|
||||||
data_len = <size_t>self.py_buf.len
|
|
||||||
|
|
||||||
nb = cparser.http_parser_execute(
|
|
||||||
self._cparser,
|
|
||||||
self._csettings,
|
|
||||||
<char*>self.py_buf.buf,
|
|
||||||
data_len)
|
|
||||||
|
|
||||||
PyBuffer_Release(&self.py_buf)
|
|
||||||
|
|
||||||
if (self._cparser.http_errno != cparser.HPE_OK):
|
|
||||||
if self._payload_error == 0:
|
|
||||||
if self._last_error is not None:
|
|
||||||
ex = self._last_error
|
|
||||||
self._last_error = None
|
|
||||||
else:
|
|
||||||
ex = parser_error_from_errno(
|
|
||||||
<cparser.http_errno> self._cparser.http_errno)
|
|
||||||
self._payload = None
|
|
||||||
raise ex
|
|
||||||
|
|
||||||
if self._messages:
|
|
||||||
messages = self._messages
|
|
||||||
self._messages = []
|
|
||||||
else:
|
|
||||||
messages = ()
|
|
||||||
|
|
||||||
if self._upgraded:
|
|
||||||
return messages, True, data[nb:]
|
|
||||||
else:
|
|
||||||
return messages, False, b''
|
|
||||||
|
|
||||||
def set_upgraded(self, val):
|
|
||||||
self._upgraded = val
|
|
||||||
|
|
||||||
|
|
||||||
cdef class HttpRequestParser(HttpParser):
|
|
||||||
|
|
||||||
def __init__(self, protocol, loop, int limit, timer=None,
|
|
||||||
size_t max_line_size=8190, size_t max_headers=32768,
|
|
||||||
size_t max_field_size=8190, payload_exception=None,
|
|
||||||
bint response_with_body=True, bint read_until_eof=False,
|
|
||||||
):
|
|
||||||
self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer,
|
|
||||||
max_line_size, max_headers, max_field_size,
|
|
||||||
payload_exception, response_with_body, read_until_eof)
|
|
||||||
|
|
||||||
cdef object _on_status_complete(self):
|
|
||||||
cdef Py_buffer py_buf
|
|
||||||
if not self._buf:
|
|
||||||
return
|
|
||||||
self._path = self._buf.decode('utf-8', 'surrogateescape')
|
|
||||||
if self._cparser.method == 5: # CONNECT
|
|
||||||
self._url = URL(self._path)
|
|
||||||
else:
|
|
||||||
PyObject_GetBuffer(self._buf, &py_buf, PyBUF_SIMPLE)
|
|
||||||
try:
|
|
||||||
self._url = _parse_url(<char*>py_buf.buf,
|
|
||||||
py_buf.len)
|
|
||||||
finally:
|
|
||||||
PyBuffer_Release(&py_buf)
|
|
||||||
PyByteArray_Resize(self._buf, 0)
|
|
||||||
|
|
||||||
|
|
||||||
cdef class HttpResponseParser(HttpParser):
|
|
||||||
|
|
||||||
def __init__(self, protocol, loop, int limit, timer=None,
|
|
||||||
size_t max_line_size=8190, size_t max_headers=32768,
|
|
||||||
size_t max_field_size=8190, payload_exception=None,
|
|
||||||
bint response_with_body=True, bint read_until_eof=False,
|
|
||||||
bint auto_decompress=True
|
|
||||||
):
|
|
||||||
self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer,
|
|
||||||
max_line_size, max_headers, max_field_size,
|
|
||||||
payload_exception, response_with_body, read_until_eof,
|
|
||||||
auto_decompress)
|
|
||||||
|
|
||||||
cdef object _on_status_complete(self):
|
|
||||||
if self._buf:
|
|
||||||
self._reason = self._buf.decode('utf-8', 'surrogateescape')
|
|
||||||
PyByteArray_Resize(self._buf, 0)
|
|
||||||
else:
|
|
||||||
self._reason = self._reason or ''
|
|
||||||
|
|
||||||
cdef int cb_on_message_begin(cparser.http_parser* parser) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
|
|
||||||
pyparser._started = True
|
|
||||||
pyparser._headers = CIMultiDict()
|
|
||||||
pyparser._raw_headers = []
|
|
||||||
PyByteArray_Resize(pyparser._buf, 0)
|
|
||||||
pyparser._path = None
|
|
||||||
pyparser._reason = None
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_url(cparser.http_parser* parser,
|
|
||||||
const char *at, size_t length) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
try:
|
|
||||||
if length > pyparser._max_line_size:
|
|
||||||
raise LineTooLong(
|
|
||||||
'Status line is too long', pyparser._max_line_size, length)
|
|
||||||
extend(pyparser._buf, at, length)
|
|
||||||
except BaseException as ex:
|
|
||||||
pyparser._last_error = ex
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_status(cparser.http_parser* parser,
|
|
||||||
const char *at, size_t length) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
cdef str reason
|
|
||||||
try:
|
|
||||||
if length > pyparser._max_line_size:
|
|
||||||
raise LineTooLong(
|
|
||||||
'Status line is too long', pyparser._max_line_size, length)
|
|
||||||
extend(pyparser._buf, at, length)
|
|
||||||
except BaseException as ex:
|
|
||||||
pyparser._last_error = ex
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_header_field(cparser.http_parser* parser,
|
|
||||||
const char *at, size_t length) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
cdef Py_ssize_t size
|
|
||||||
try:
|
|
||||||
pyparser._on_status_complete()
|
|
||||||
size = len(pyparser._raw_name) + length
|
|
||||||
if size > pyparser._max_field_size:
|
|
||||||
raise LineTooLong(
|
|
||||||
'Header name is too long', pyparser._max_field_size, size)
|
|
||||||
pyparser._on_header_field(at, length)
|
|
||||||
except BaseException as ex:
|
|
||||||
pyparser._last_error = ex
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_header_value(cparser.http_parser* parser,
|
|
||||||
const char *at, size_t length) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
cdef Py_ssize_t size
|
|
||||||
try:
|
|
||||||
size = len(pyparser._raw_value) + length
|
|
||||||
if size > pyparser._max_field_size:
|
|
||||||
raise LineTooLong(
|
|
||||||
'Header value is too long', pyparser._max_field_size, size)
|
|
||||||
pyparser._on_header_value(at, length)
|
|
||||||
except BaseException as ex:
|
|
||||||
pyparser._last_error = ex
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_headers_complete(cparser.http_parser* parser) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
try:
|
|
||||||
pyparser._on_status_complete()
|
|
||||||
pyparser._on_headers_complete()
|
|
||||||
except BaseException as exc:
|
|
||||||
pyparser._last_error = exc
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
if pyparser._cparser.upgrade or pyparser._cparser.method == 5: # CONNECT
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_body(cparser.http_parser* parser,
|
|
||||||
const char *at, size_t length) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
cdef bytes body = at[:length]
|
|
||||||
try:
|
|
||||||
pyparser._payload.feed_data(body, length)
|
|
||||||
except BaseException as exc:
|
|
||||||
if pyparser._payload_exception is not None:
|
|
||||||
pyparser._payload.set_exception(pyparser._payload_exception(str(exc)))
|
|
||||||
else:
|
|
||||||
pyparser._payload.set_exception(exc)
|
|
||||||
pyparser._payload_error = 1
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_message_complete(cparser.http_parser* parser) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
try:
|
|
||||||
pyparser._started = False
|
|
||||||
pyparser._on_message_complete()
|
|
||||||
except BaseException as exc:
|
|
||||||
pyparser._last_error = exc
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_chunk_header(cparser.http_parser* parser) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
try:
|
|
||||||
pyparser._on_chunk_header()
|
|
||||||
except BaseException as exc:
|
|
||||||
pyparser._last_error = exc
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef int cb_on_chunk_complete(cparser.http_parser* parser) except -1:
|
|
||||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
|
||||||
try:
|
|
||||||
pyparser._on_chunk_complete()
|
|
||||||
except BaseException as exc:
|
|
||||||
pyparser._last_error = exc
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef parser_error_from_errno(cparser.http_errno errno):
|
|
||||||
cdef bytes desc = cparser.http_errno_description(errno)
|
|
||||||
|
|
||||||
if errno in (cparser.HPE_CB_message_begin,
|
|
||||||
cparser.HPE_CB_url,
|
|
||||||
cparser.HPE_CB_header_field,
|
|
||||||
cparser.HPE_CB_header_value,
|
|
||||||
cparser.HPE_CB_headers_complete,
|
|
||||||
cparser.HPE_CB_body,
|
|
||||||
cparser.HPE_CB_message_complete,
|
|
||||||
cparser.HPE_CB_status,
|
|
||||||
cparser.HPE_CB_chunk_header,
|
|
||||||
cparser.HPE_CB_chunk_complete):
|
|
||||||
cls = BadHttpMessage
|
|
||||||
|
|
||||||
elif errno == cparser.HPE_INVALID_STATUS:
|
|
||||||
cls = BadStatusLine
|
|
||||||
|
|
||||||
elif errno == cparser.HPE_INVALID_METHOD:
|
|
||||||
cls = BadStatusLine
|
|
||||||
|
|
||||||
elif errno == cparser.HPE_INVALID_URL:
|
|
||||||
cls = InvalidURLError
|
|
||||||
|
|
||||||
else:
|
|
||||||
cls = BadHttpMessage
|
|
||||||
|
|
||||||
return cls(desc.decode('latin-1'))
|
|
||||||
|
|
||||||
|
|
||||||
def parse_url(url):
|
|
||||||
cdef:
|
|
||||||
Py_buffer py_buf
|
|
||||||
char* buf_data
|
|
||||||
|
|
||||||
PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
|
|
||||||
try:
|
|
||||||
buf_data = <char*>py_buf.buf
|
|
||||||
return _parse_url(buf_data, py_buf.len)
|
|
||||||
finally:
|
|
||||||
PyBuffer_Release(&py_buf)
|
|
||||||
|
|
||||||
|
|
||||||
cdef _parse_url(char* buf_data, size_t length):
|
|
||||||
cdef:
|
|
||||||
cparser.http_parser_url* parsed
|
|
||||||
int res
|
|
||||||
str schema = None
|
|
||||||
str host = None
|
|
||||||
object port = None
|
|
||||||
str path = None
|
|
||||||
str query = None
|
|
||||||
str fragment = None
|
|
||||||
str user = None
|
|
||||||
str password = None
|
|
||||||
str userinfo = None
|
|
||||||
object result = None
|
|
||||||
int off
|
|
||||||
int ln
|
|
||||||
|
|
||||||
parsed = <cparser.http_parser_url*> \
|
|
||||||
PyMem_Malloc(sizeof(cparser.http_parser_url))
|
|
||||||
if parsed is NULL:
|
|
||||||
raise MemoryError()
|
|
||||||
cparser.http_parser_url_init(parsed)
|
|
||||||
try:
|
|
||||||
res = cparser.http_parser_parse_url(buf_data, length, 0, parsed)
|
|
||||||
|
|
||||||
if res == 0:
|
|
||||||
if parsed.field_set & (1 << cparser.UF_SCHEMA):
|
|
||||||
off = parsed.field_data[<int>cparser.UF_SCHEMA].off
|
|
||||||
ln = parsed.field_data[<int>cparser.UF_SCHEMA].len
|
|
||||||
schema = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
|
|
||||||
else:
|
|
||||||
schema = ''
|
|
||||||
|
|
||||||
if parsed.field_set & (1 << cparser.UF_HOST):
|
|
||||||
off = parsed.field_data[<int>cparser.UF_HOST].off
|
|
||||||
ln = parsed.field_data[<int>cparser.UF_HOST].len
|
|
||||||
host = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
|
|
||||||
else:
|
|
||||||
host = ''
|
|
||||||
|
|
||||||
if parsed.field_set & (1 << cparser.UF_PORT):
|
|
||||||
port = parsed.port
|
|
||||||
|
|
||||||
if parsed.field_set & (1 << cparser.UF_PATH):
|
|
||||||
off = parsed.field_data[<int>cparser.UF_PATH].off
|
|
||||||
ln = parsed.field_data[<int>cparser.UF_PATH].len
|
|
||||||
path = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
|
|
||||||
else:
|
|
||||||
path = ''
|
|
||||||
|
|
||||||
if parsed.field_set & (1 << cparser.UF_QUERY):
|
|
||||||
off = parsed.field_data[<int>cparser.UF_QUERY].off
|
|
||||||
ln = parsed.field_data[<int>cparser.UF_QUERY].len
|
|
||||||
query = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
|
|
||||||
else:
|
|
||||||
query = ''
|
|
||||||
|
|
||||||
if parsed.field_set & (1 << cparser.UF_FRAGMENT):
|
|
||||||
off = parsed.field_data[<int>cparser.UF_FRAGMENT].off
|
|
||||||
ln = parsed.field_data[<int>cparser.UF_FRAGMENT].len
|
|
||||||
fragment = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
|
|
||||||
else:
|
|
||||||
fragment = ''
|
|
||||||
|
|
||||||
if parsed.field_set & (1 << cparser.UF_USERINFO):
|
|
||||||
off = parsed.field_data[<int>cparser.UF_USERINFO].off
|
|
||||||
ln = parsed.field_data[<int>cparser.UF_USERINFO].len
|
|
||||||
userinfo = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
|
|
||||||
|
|
||||||
user, sep, password = userinfo.partition(':')
|
|
||||||
|
|
||||||
return URL_build(scheme=schema,
|
|
||||||
user=user, password=password, host=host, port=port,
|
|
||||||
path=path, query_string=query, fragment=fragment, encoded=True)
|
|
||||||
else:
|
|
||||||
raise InvalidURLError("invalid url {!r}".format(buf_data))
|
|
||||||
finally:
|
|
||||||
PyMem_Free(parsed)
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -1,151 +0,0 @@
|
||||||
from cpython.bytes cimport PyBytes_FromStringAndSize
|
|
||||||
from cpython.exc cimport PyErr_NoMemory
|
|
||||||
from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc
|
|
||||||
from cpython.object cimport PyObject_Str
|
|
||||||
from libc.stdint cimport uint8_t, uint64_t
|
|
||||||
from libc.string cimport memcpy
|
|
||||||
|
|
||||||
from multidict import istr
|
|
||||||
|
|
||||||
DEF BUF_SIZE = 16 * 1024 # 16KiB
|
|
||||||
cdef char BUFFER[BUF_SIZE]
|
|
||||||
|
|
||||||
cdef object _istr = istr
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------- writer ---------------------------
|
|
||||||
|
|
||||||
cdef struct Writer:
|
|
||||||
char *buf
|
|
||||||
Py_ssize_t size
|
|
||||||
Py_ssize_t pos
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline void _init_writer(Writer* writer):
|
|
||||||
writer.buf = &BUFFER[0]
|
|
||||||
writer.size = BUF_SIZE
|
|
||||||
writer.pos = 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline void _release_writer(Writer* writer):
|
|
||||||
if writer.buf != BUFFER:
|
|
||||||
PyMem_Free(writer.buf)
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline int _write_byte(Writer* writer, uint8_t ch):
|
|
||||||
cdef char * buf
|
|
||||||
cdef Py_ssize_t size
|
|
||||||
|
|
||||||
if writer.pos == writer.size:
|
|
||||||
# reallocate
|
|
||||||
size = writer.size + BUF_SIZE
|
|
||||||
if writer.buf == BUFFER:
|
|
||||||
buf = <char*>PyMem_Malloc(size)
|
|
||||||
if buf == NULL:
|
|
||||||
PyErr_NoMemory()
|
|
||||||
return -1
|
|
||||||
memcpy(buf, writer.buf, writer.size)
|
|
||||||
else:
|
|
||||||
buf = <char*>PyMem_Realloc(writer.buf, size)
|
|
||||||
if buf == NULL:
|
|
||||||
PyErr_NoMemory()
|
|
||||||
return -1
|
|
||||||
writer.buf = buf
|
|
||||||
writer.size = size
|
|
||||||
writer.buf[writer.pos] = <char>ch
|
|
||||||
writer.pos += 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol):
|
|
||||||
cdef uint64_t utf = <uint64_t> symbol
|
|
||||||
|
|
||||||
if utf < 0x80:
|
|
||||||
return _write_byte(writer, <uint8_t>utf)
|
|
||||||
elif utf < 0x800:
|
|
||||||
if _write_byte(writer, <uint8_t>(0xc0 | (utf >> 6))) < 0:
|
|
||||||
return -1
|
|
||||||
return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))
|
|
||||||
elif 0xD800 <= utf <= 0xDFFF:
|
|
||||||
# surogate pair, ignored
|
|
||||||
return 0
|
|
||||||
elif utf < 0x10000:
|
|
||||||
if _write_byte(writer, <uint8_t>(0xe0 | (utf >> 12))) < 0:
|
|
||||||
return -1
|
|
||||||
if _write_byte(writer, <uint8_t>(0x80 | ((utf >> 6) & 0x3f))) < 0:
|
|
||||||
return -1
|
|
||||||
return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))
|
|
||||||
elif utf > 0x10FFFF:
|
|
||||||
# symbol is too large
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
if _write_byte(writer, <uint8_t>(0xf0 | (utf >> 18))) < 0:
|
|
||||||
return -1
|
|
||||||
if _write_byte(writer,
|
|
||||||
<uint8_t>(0x80 | ((utf >> 12) & 0x3f))) < 0:
|
|
||||||
return -1
|
|
||||||
if _write_byte(writer,
|
|
||||||
<uint8_t>(0x80 | ((utf >> 6) & 0x3f))) < 0:
|
|
||||||
return -1
|
|
||||||
return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline int _write_str(Writer* writer, str s):
|
|
||||||
cdef Py_UCS4 ch
|
|
||||||
for ch in s:
|
|
||||||
if _write_utf8(writer, ch) < 0:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
|
|
||||||
# --------------- _serialize_headers ----------------------
|
|
||||||
|
|
||||||
cdef str to_str(object s):
|
|
||||||
typ = type(s)
|
|
||||||
if typ is str:
|
|
||||||
return <str>s
|
|
||||||
elif typ is _istr:
|
|
||||||
return PyObject_Str(s)
|
|
||||||
elif not isinstance(s, str):
|
|
||||||
raise TypeError("Cannot serialize non-str key {!r}".format(s))
|
|
||||||
else:
|
|
||||||
return str(s)
|
|
||||||
|
|
||||||
|
|
||||||
def _serialize_headers(str status_line, headers):
|
|
||||||
cdef Writer writer
|
|
||||||
cdef object key
|
|
||||||
cdef object val
|
|
||||||
cdef bytes ret
|
|
||||||
|
|
||||||
_init_writer(&writer)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if _write_str(&writer, status_line) < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b'\r') < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b'\n') < 0:
|
|
||||||
raise
|
|
||||||
|
|
||||||
for key, val in headers.items():
|
|
||||||
if _write_str(&writer, to_str(key)) < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b':') < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b' ') < 0:
|
|
||||||
raise
|
|
||||||
if _write_str(&writer, to_str(val)) < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b'\r') < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b'\n') < 0:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if _write_byte(&writer, b'\r') < 0:
|
|
||||||
raise
|
|
||||||
if _write_byte(&writer, b'\n') < 0:
|
|
||||||
raise
|
|
||||||
|
|
||||||
return PyBytes_FromStringAndSize(writer.buf, writer.pos)
|
|
||||||
finally:
|
|
||||||
_release_writer(&writer)
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -1,56 +0,0 @@
|
||||||
from cpython cimport PyBytes_AsString
|
|
||||||
|
|
||||||
|
|
||||||
#from cpython cimport PyByteArray_AsString # cython still not exports that
|
|
||||||
cdef extern from "Python.h":
|
|
||||||
char* PyByteArray_AsString(bytearray ba) except NULL
|
|
||||||
|
|
||||||
from libc.stdint cimport uint32_t, uint64_t, uintmax_t
|
|
||||||
|
|
||||||
|
|
||||||
def _websocket_mask_cython(object mask, object data):
|
|
||||||
"""Note, this function mutates its `data` argument
|
|
||||||
"""
|
|
||||||
cdef:
|
|
||||||
Py_ssize_t data_len, i
|
|
||||||
# bit operations on signed integers are implementation-specific
|
|
||||||
unsigned char * in_buf
|
|
||||||
const unsigned char * mask_buf
|
|
||||||
uint32_t uint32_msk
|
|
||||||
uint64_t uint64_msk
|
|
||||||
|
|
||||||
assert len(mask) == 4
|
|
||||||
|
|
||||||
if not isinstance(mask, bytes):
|
|
||||||
mask = bytes(mask)
|
|
||||||
|
|
||||||
if isinstance(data, bytearray):
|
|
||||||
data = <bytearray>data
|
|
||||||
else:
|
|
||||||
data = bytearray(data)
|
|
||||||
|
|
||||||
data_len = len(data)
|
|
||||||
in_buf = <unsigned char*>PyByteArray_AsString(data)
|
|
||||||
mask_buf = <const unsigned char*>PyBytes_AsString(mask)
|
|
||||||
uint32_msk = (<uint32_t*>mask_buf)[0]
|
|
||||||
|
|
||||||
# TODO: align in_data ptr to achieve even faster speeds
|
|
||||||
# does it need in python ?! malloc() always aligns to sizeof(long) bytes
|
|
||||||
|
|
||||||
if sizeof(size_t) >= 8:
|
|
||||||
uint64_msk = uint32_msk
|
|
||||||
uint64_msk = (uint64_msk << 32) | uint32_msk
|
|
||||||
|
|
||||||
while data_len >= 8:
|
|
||||||
(<uint64_t*>in_buf)[0] ^= uint64_msk
|
|
||||||
in_buf += 8
|
|
||||||
data_len -= 8
|
|
||||||
|
|
||||||
|
|
||||||
while data_len >= 4:
|
|
||||||
(<uint32_t*>in_buf)[0] ^= uint32_msk
|
|
||||||
in_buf += 4
|
|
||||||
data_len -= 4
|
|
||||||
|
|
||||||
for i in range(0, data_len):
|
|
||||||
in_buf[i] ^= mask_buf[i]
|
|
|
@ -1,200 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from collections.abc import Sized
|
|
||||||
from http.cookies import BaseCookie, Morsel
|
|
||||||
from typing import (
|
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Generator,
|
|
||||||
Iterable,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
)
|
|
||||||
|
|
||||||
from multidict import CIMultiDict
|
|
||||||
from yarl import URL
|
|
||||||
|
|
||||||
from .helpers import get_running_loop
|
|
||||||
from .typedefs import LooseCookies
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
|
||||||
from .web_app import Application
|
|
||||||
from .web_exceptions import HTTPException
|
|
||||||
from .web_request import BaseRequest, Request
|
|
||||||
from .web_response import StreamResponse
|
|
||||||
else:
|
|
||||||
BaseRequest = Request = Application = StreamResponse = None
|
|
||||||
HTTPException = None
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractRouter(ABC):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._frozen = False
|
|
||||||
|
|
||||||
def post_init(self, app: Application) -> None:
|
|
||||||
"""Post init stage.
|
|
||||||
|
|
||||||
Not an abstract method for sake of backward compatibility,
|
|
||||||
but if the router wants to be aware of the application
|
|
||||||
it can override this.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def frozen(self) -> bool:
|
|
||||||
return self._frozen
|
|
||||||
|
|
||||||
def freeze(self) -> None:
|
|
||||||
"""Freeze router."""
|
|
||||||
self._frozen = True
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def resolve(self, request: Request) -> "AbstractMatchInfo":
|
|
||||||
"""Return MATCH_INFO for given request"""
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractMatchInfo(ABC):
|
|
||||||
@property # pragma: no branch
|
|
||||||
@abstractmethod
|
|
||||||
def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]:
|
|
||||||
"""Execute matched request handler"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def expect_handler(self) -> Callable[[Request], Awaitable[None]]:
|
|
||||||
"""Expect handler for 100-continue processing"""
|
|
||||||
|
|
||||||
@property # pragma: no branch
|
|
||||||
@abstractmethod
|
|
||||||
def http_exception(self) -> Optional[HTTPException]:
|
|
||||||
"""HTTPException instance raised on router's resolving, or None"""
|
|
||||||
|
|
||||||
@abstractmethod # pragma: no branch
|
|
||||||
def get_info(self) -> Dict[str, Any]:
|
|
||||||
"""Return a dict with additional info useful for introspection"""
|
|
||||||
|
|
||||||
@property # pragma: no branch
|
|
||||||
@abstractmethod
|
|
||||||
def apps(self) -> Tuple[Application, ...]:
|
|
||||||
"""Stack of nested applications.
|
|
||||||
|
|
||||||
Top level application is left-most element.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def add_app(self, app: Application) -> None:
|
|
||||||
"""Add application to the nested apps stack."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def freeze(self) -> None:
|
|
||||||
"""Freeze the match info.
|
|
||||||
|
|
||||||
The method is called after route resolution.
|
|
||||||
|
|
||||||
After the call .add_app() is forbidden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractView(ABC):
|
|
||||||
"""Abstract class based view."""
|
|
||||||
|
|
||||||
def __init__(self, request: Request) -> None:
|
|
||||||
self._request = request
|
|
||||||
|
|
||||||
@property
|
|
||||||
def request(self) -> Request:
|
|
||||||
"""Request instance."""
|
|
||||||
return self._request
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def __await__(self) -> Generator[Any, None, StreamResponse]:
|
|
||||||
"""Execute the view handler."""
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractResolver(ABC):
|
|
||||||
"""Abstract DNS resolver."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def resolve(self, host: str, port: int, family: int) -> List[Dict[str, Any]]:
|
|
||||||
"""Return IP address for given hostname"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def close(self) -> None:
|
|
||||||
"""Release resolver"""
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
|
||||||
IterableBase = Iterable[Morsel[str]]
|
|
||||||
else:
|
|
||||||
IterableBase = Iterable
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractCookieJar(Sized, IterableBase):
|
|
||||||
"""Abstract Cookie Jar."""
|
|
||||||
|
|
||||||
def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
|
||||||
self._loop = get_running_loop(loop)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def clear(self) -> None:
|
|
||||||
"""Clear all cookies."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
|
|
||||||
"""Update cookies."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def filter_cookies(self, request_url: URL) -> "BaseCookie[str]":
|
|
||||||
"""Return the jar's cookies filtered by their attributes."""
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractStreamWriter(ABC):
|
|
||||||
"""Abstract stream writer."""
|
|
||||||
|
|
||||||
buffer_size = 0
|
|
||||||
output_size = 0
|
|
||||||
length = 0 # type: Optional[int]
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def write(self, chunk: bytes) -> None:
|
|
||||||
"""Write chunk into stream."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def write_eof(self, chunk: bytes = b"") -> None:
|
|
||||||
"""Write last chunk."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def drain(self) -> None:
|
|
||||||
"""Flush the write buffer."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def enable_compression(self, encoding: str = "deflate") -> None:
|
|
||||||
"""Enable HTTP body compression"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def enable_chunking(self) -> None:
|
|
||||||
"""Enable HTTP chunked mode"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def write_headers(
|
|
||||||
self, status_line: str, headers: "CIMultiDict[str]"
|
|
||||||
) -> None:
|
|
||||||
"""Write HTTP headers"""
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractAccessLogger(ABC):
|
|
||||||
"""Abstract writer to access log."""
|
|
||||||
|
|
||||||
def __init__(self, logger: logging.Logger, log_format: str) -> None:
|
|
||||||
self.logger = logger
|
|
||||||
self.log_format = log_format
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
|
|
||||||
"""Emit log to logger."""
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue