mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2025-08-18 11:16:37 +00:00
Initial Configuration Push
This commit is contained in:
1
.HA_VERSION
Executable file
1
.HA_VERSION
Executable file
@@ -0,0 +1 @@
|
||||
0.30.1
|
12
.gitignore
vendored
Executable file
12
.gitignore
vendored
Executable file
@@ -0,0 +1,12 @@
|
||||
*.pid
|
||||
*.xml
|
||||
OZW_Log.txt
|
||||
home-assistant.log
|
||||
home-assistant_v2.db
|
||||
*.db-journal
|
||||
lib
|
||||
secrets.yaml
|
||||
known_devices.yaml
|
||||
phue.conf
|
||||
pyozw.sqlite
|
||||
|
19
README.md
Normal file → Executable file
19
README.md
Normal file → Executable file
@@ -1,2 +1,21 @@
|
||||
# Home-AssistantConfig
|
||||
Home Assistant configuration files (YAMLs)
|
||||
|
||||
This is my Home Assistant Configuration. I update it pretty regularly.
|
||||
Home Assistantruns on my Raspberry Pi 3 with Z Wave Stick. I've also added a 433Mhz Transmitter and receiver.
|
||||
|
||||
Software on the Pi : Home Assistant, Dasher, HomeBridge
|
||||
|
||||
Devices I have :
|
||||
* Lots of iOS Devices
|
||||
* Nest Thermostat
|
||||
* Amazon Echo
|
||||
* Phillips Hue Hub
|
||||
* Circle by Disney
|
||||
* Rachio Sprinkler system
|
||||
* SkyBell HD
|
||||
* Rokus for all streaming
|
||||
* ChromeCast Audios
|
||||
* Etekcity Outlets
|
||||
* Amazon Dash Buttons
|
||||
|
||||
|
528
configuration.yaml
Executable file
528
configuration.yaml
Executable file
@@ -0,0 +1,528 @@
|
||||
homeassistant:
|
||||
name: Bear Stone Run
|
||||
latitude: !secret homeassistant_latitude
|
||||
longitude: !secret homeassistant_longitude
|
||||
elevation: !secret homeassistant_elevation
|
||||
unit_system: imperial
|
||||
time_zone: America/New_York
|
||||
|
||||
customize:
|
||||
climate.downstairs:
|
||||
friendly_name: 'Nest Downstairs'
|
||||
icon: mdi:air-conditioner
|
||||
climate.upstairs:
|
||||
friendly_name: 'Nest Upstairs'
|
||||
icon: mdi:air-conditioner
|
||||
sensor.dark_sky_precip_intensity:
|
||||
friendly_name: 'Rainfall'
|
||||
sensor.dark_sky_humidity:
|
||||
friendly_name: 'Outdoor Humidity'
|
||||
sensor.dark_sky_temperature:
|
||||
friendly_name: 'Outdoor Temp'
|
||||
sensor.speedtest_download:
|
||||
friendly_name: 'Download'
|
||||
icon: mdi:speedometer
|
||||
sensor.speedtest_upload:
|
||||
friendly_name: 'Upload'
|
||||
icon: mdi:speedometer
|
||||
media_player.roku_1gj361038190:
|
||||
friendly_name: 'Downstairs Main Roku'
|
||||
media_player.roku_1gs3ac111661:
|
||||
friendly_name: 'Upstairs Roku'
|
||||
media_player.roku_2N006T621680:
|
||||
friendly_name: 'Bedroom Roku'
|
||||
sensor.wii:
|
||||
icon: mdi:gamepad-variant
|
||||
sensor.tablotv:
|
||||
icon: mdi:television-guide
|
||||
sensor.hue_hub:
|
||||
icon: mdi:router-wireless
|
||||
sensor.rachio:
|
||||
icon: mdi:spray
|
||||
sensor.circle:
|
||||
icon: mdi:google-circles-group
|
||||
sensor.alexa_echo:
|
||||
icon: mdi:amazon
|
||||
sensor.skybell:
|
||||
icon: mdi:camera-front
|
||||
sensor.samsungtv:
|
||||
icon: mdi:television
|
||||
sensor.since_last_boot_templated:
|
||||
friendly_name: 'HomeAssistant Uptime'
|
||||
icon: mdi:clock-start
|
||||
sensor.since_last_boot:
|
||||
hidden: true
|
||||
sensor.badlogin:
|
||||
hidden: true
|
||||
device_tracker.tablotv:
|
||||
hidden: true
|
||||
device_tracker.hue_hub:
|
||||
hidden: true
|
||||
device_tracker.wii:
|
||||
hidden: true
|
||||
device_tracker.rachio:
|
||||
hidden: true
|
||||
device_tracker.circle:
|
||||
hidden: true
|
||||
device_tracker.alexa_echo:
|
||||
hidden: true
|
||||
device_tracker.skybell:
|
||||
hidden: true
|
||||
device_tracker.samsungtv:
|
||||
hidden: true
|
||||
|
||||
http:
|
||||
api_password: !secret http_api_password
|
||||
|
||||
discovery:
|
||||
|
||||
light:
|
||||
platform: hue
|
||||
host: 192.168.10.101
|
||||
allow_unreachable: true
|
||||
|
||||
ifttt:
|
||||
key: !secret ifttt_key
|
||||
|
||||
device_tracker:
|
||||
platform: nmap_tracker
|
||||
hosts: 192.168.10.100-254
|
||||
track_new_devices: no
|
||||
# consider_home: 1800 - Added to known_devices.yaml instead on a device by device basis.
|
||||
|
||||
frontend:
|
||||
# history:
|
||||
logbook:
|
||||
exclude:
|
||||
entities:
|
||||
- sensor.since_last_boot
|
||||
- sensor.since_last_boot_templated
|
||||
#logger:
|
||||
# default: info
|
||||
|
||||
sun:
|
||||
|
||||
mqtt:
|
||||
broker: 127.0.0.1
|
||||
port: 1883
|
||||
client_id: home-assistant-1
|
||||
username: pi
|
||||
password: raspberry
|
||||
|
||||
zwave:
|
||||
usb_path: /dev/ttyACM0
|
||||
config_path: /srv/hass/hass_venv/lib/python3.4/site-packages/libopenzwave-0.3.1-py3.4-linux-armv7l.egg/config
|
||||
|
||||
sensor forecast:
|
||||
platform: darksky
|
||||
api_key: !secret forecast_key
|
||||
|
||||
monitored_conditions:
|
||||
- summary
|
||||
# - precip_type
|
||||
- precip_intensity
|
||||
- temperature
|
||||
# - dew_point
|
||||
# - wind_speed
|
||||
# - wind_bearing
|
||||
# - cloud_cover
|
||||
- humidity
|
||||
# - pressure
|
||||
# - visibility
|
||||
# - ozone
|
||||
|
||||
nest:
|
||||
username: !secret nest_username
|
||||
password: !secret nest_password
|
||||
|
||||
climate:
|
||||
platform: nest
|
||||
|
||||
media_player:
|
||||
platform: cast
|
||||
|
||||
recorder:
|
||||
purge_days: 14
|
||||
|
||||
sensor Speedtest:
|
||||
platform: speedtest
|
||||
minute: 30
|
||||
hour:
|
||||
- 0
|
||||
- 6
|
||||
- 12
|
||||
- 18
|
||||
monitored_conditions:
|
||||
- download
|
||||
- upload
|
||||
|
||||
# emulated_hue:
|
||||
# host_ip: 192.168.10.10
|
||||
# listen_port: 8300
|
||||
# off_maps_to_on_domains:
|
||||
# - script
|
||||
# - scene
|
||||
|
||||
# A comma separated list of states that have to be tracked as a single group
|
||||
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
|
||||
group:
|
||||
Family:
|
||||
- device_tracker.carlo
|
||||
- device_tracker.stacey
|
||||
- device_tracker.franco
|
||||
- device_tracker.yolanda
|
||||
- device_tracker.joyce_ipad
|
||||
Devices:
|
||||
- sensor.since_last_boot_templated
|
||||
- sensor.wii
|
||||
- sensor.hue_hub
|
||||
- sensor.tablotv
|
||||
- sensor.alexa_echo
|
||||
- sensor.circle
|
||||
- sensor.rachio
|
||||
- sensor.skybell
|
||||
- sensor.samsungtv
|
||||
Internet:
|
||||
- sensor.speedtest_download
|
||||
- sensor.speedtest_upload
|
||||
Sensors:
|
||||
- binary_sensor._sensor_2
|
||||
- binary_sensor._sensor_3
|
||||
- binary_sensor._sensor_5
|
||||
- binary_sensor.aeotec_dsb04100_doorwindow_sensor_sensor_4
|
||||
- binary_sensor.aeotec_dsb04100_doorwindow_sensor_sensor_6
|
||||
Nest:
|
||||
- climate.downstairs
|
||||
- climate.upstairs
|
||||
|
||||
switch:
|
||||
platform: rpi_rf
|
||||
gpio: 17
|
||||
switches:
|
||||
Outlet_Living_Room:
|
||||
protocol: 1
|
||||
pulselength: 186
|
||||
code_on: 5265155
|
||||
code_off: 5265164
|
||||
Outlet_2:
|
||||
protocol: 1
|
||||
pulselength: 186
|
||||
code_on: 5264835
|
||||
code_off: 5264844
|
||||
Outlet_Garage:
|
||||
protocol: 1
|
||||
pulselength: 186
|
||||
code_on: 5264691
|
||||
code_off: 5264700
|
||||
|
||||
sensor Devices:
|
||||
platform: template
|
||||
sensors:
|
||||
wii:
|
||||
friendly_name: 'Wii'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.wii", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
tablotv:
|
||||
friendly_name: 'Tablo TV'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.tablotv", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
hue_hub:
|
||||
friendly_name: 'Hue Hub'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.hue_hub", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
alexa_echo:
|
||||
friendly_name: 'Alexa Echo'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.alexa_echo", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
circle:
|
||||
friendly_name: 'Disney Circle'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.circle", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
rachio:
|
||||
friendly_name: 'Rachio Sprinklers'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.rachio", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
skybell:
|
||||
friendly_name: 'Skybell Doorbell'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.skybell", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
samsungtv:
|
||||
friendly_name: 'Samsung TV'
|
||||
value_template: >-
|
||||
{%- if is_state("device_tracker.samsungtv", "home") %}
|
||||
Online
|
||||
{% else %}
|
||||
Offline
|
||||
{%- endif %}
|
||||
|
||||
|
||||
sensor Login_Failures:
|
||||
platform: command_line
|
||||
command: "grep -c invalidpassword /home/hass/.homeassistant/home-assistant.log"
|
||||
name: badlogin
|
||||
|
||||
sensor:
|
||||
- platform: systemmonitor
|
||||
resources:
|
||||
# - type: last_boot
|
||||
- type: since_last_boot
|
||||
|
||||
- platform: template
|
||||
sensors:
|
||||
since_last_boot_templated:
|
||||
value_template: >-
|
||||
{%- set slb = states.sensor.since_last_boot.state.split(' ') -%}
|
||||
{%- set count = slb | length -%}
|
||||
{%- set hms = slb[count - 1] -%}
|
||||
{%- set hms_trimmed = hms.split('.')[0] -%}
|
||||
{%- set hms_split = hms_trimmed.split(':') -%}
|
||||
{%- set hours = hms_split[0] | int -%}
|
||||
{%- set minutes = hms_split[1] | int -%}
|
||||
{%- set seconds = hms_split[2] | int -%}
|
||||
|
||||
{%- if count == 3 -%}
|
||||
{{ slb[0] ~ ' ' ~ slb[1] ~ ' ' }}
|
||||
{%- endif -%}
|
||||
{%- if hours > 0 -%}
|
||||
{%- if hours == 1 -%}
|
||||
1 hour
|
||||
{%- else -%}
|
||||
{{ hours }} hours
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- if minutes > 0 -%}
|
||||
{%- if hours > 0 -%}
|
||||
{{ ', ' }}
|
||||
{%- endif -%}
|
||||
{%- if minutes == 1 -%}
|
||||
1 minute
|
||||
{%- else -%}
|
||||
{{ minutes }} minutes
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- if seconds > 0 -%}
|
||||
{%- if hours > 0 or minutes > 0 -%}
|
||||
{{ ', ' }}
|
||||
{%- endif -%}
|
||||
{%- if seconds == 1 -%}
|
||||
1 second
|
||||
{%- else -%}
|
||||
{{ seconds }} seconds
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
automation:
|
||||
- alias: Heal ZWave Nightly
|
||||
trigger:
|
||||
platform: time
|
||||
after: '2:31:00'
|
||||
action:
|
||||
service: zwave.heal_network
|
||||
|
||||
- alias: "Update Available Notification"
|
||||
trigger:
|
||||
platform: state
|
||||
entity_id: updater.updater
|
||||
action:
|
||||
service: ifttt.trigger
|
||||
data: {"event":"device_status", "value1":"Home Assistant Update: ", "value2":"Available"}
|
||||
|
||||
- alias: Login Failure
|
||||
trigger:
|
||||
platform: numeric_state
|
||||
entity_id: sensor.badlogin
|
||||
above: 1
|
||||
action:
|
||||
service: ifttt.trigger
|
||||
data: {"event":"device_status", "value1":"Home Assistant Error: ", "value2":"Login Failure Detected"}
|
||||
|
||||
- alias: 'Device Status'
|
||||
#This recipe sends a POST to IFTTT Maker channel. IFTTT then sends me a SMS Text with "device_status : Wii is Offline."
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
- sensor.wii
|
||||
- sensor.tablotv
|
||||
- sensor.hue_hub
|
||||
- sensor.alexa_echo
|
||||
- sensor.rachio
|
||||
- sensor.circle
|
||||
- sensor.skybell
|
||||
action:
|
||||
service: ifttt.trigger
|
||||
data_template: {"event":"device_status", "value1":"{{ trigger.entity_id.split('.')[1] }}", "value2":"{{ trigger.to_state.state }}"}
|
||||
|
||||
- alias: Startup Notification
|
||||
trigger:
|
||||
platform: event
|
||||
event_type: homeassistant_start
|
||||
action:
|
||||
service: ifttt.trigger
|
||||
data: {"event":"device_status", "value1":"Home Assistant", "value2":"Up and Running"}
|
||||
|
||||
- alias: 'GoodNight - Away Mode'
|
||||
# There is also an IFTTT recipe that shuts down all lights when Nest goes into Away mode. - event_type= Good_Night
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: group.family
|
||||
state: 'not_home'
|
||||
- platform: event
|
||||
event_type: good_night
|
||||
|
||||
condition:
|
||||
condition: state
|
||||
entity_id: group.family
|
||||
state: not_home
|
||||
|
||||
action:
|
||||
service: light.turn_off
|
||||
entity_id: group.all_lights
|
||||
|
||||
- alias: ZWave Enerwave Door Sensors Open
|
||||
trigger:
|
||||
platform: event
|
||||
event_type: zwave.node_event
|
||||
event_data:
|
||||
object_id: enerwave_unknown_type0601_id0903_2
|
||||
basic_level: 255
|
||||
action:
|
||||
service: light.turn_off
|
||||
entity_id: light.office_lamp
|
||||
|
||||
- alias: ZWave Enerwave Door Sensors Closed
|
||||
trigger:
|
||||
platform: event
|
||||
event_type: zwave.node_event
|
||||
event_data:
|
||||
object_id: enerwave_unknown_type0601_id0903_2
|
||||
basic_level: 0
|
||||
action:
|
||||
service: light.turn_on
|
||||
entity_id: light.office_lamp
|
||||
|
||||
- alias: TV Time
|
||||
trigger:
|
||||
- platform: sun
|
||||
event: sunset
|
||||
offset: '+00:30:00'
|
||||
- platform: event
|
||||
event_type: tv_time
|
||||
|
||||
condition:
|
||||
condition: state
|
||||
entity_id: group.family
|
||||
state: home
|
||||
|
||||
action:
|
||||
service: scene.turn_on
|
||||
entity_id: scene.living_room_tv_time
|
||||
|
||||
scene:
|
||||
- name: Living Room TV Time
|
||||
entities:
|
||||
light.dinette_light_1:
|
||||
state: off
|
||||
transition: 10
|
||||
light.dinette_light_2:
|
||||
state: off
|
||||
transition: 10
|
||||
light.living_room_front_left:
|
||||
state: off
|
||||
transition: 10
|
||||
light.living_room_front_right:
|
||||
state: off
|
||||
transition: 10
|
||||
light.living_room_slider:
|
||||
state: off
|
||||
transition: 10
|
||||
light.living_room_back_right:
|
||||
state: on
|
||||
transition: 10
|
||||
brightness: 1
|
||||
light.living_room_back_left:
|
||||
state: on
|
||||
transition: 40
|
||||
brightness: 1
|
||||
light.living_room_couch_1:
|
||||
state: on
|
||||
transition: 400
|
||||
xy_color: [0.6621,0.3023]
|
||||
brightness: 255
|
||||
light.living_room_couch_2:
|
||||
state: on
|
||||
transition: 400
|
||||
xy_color: [0.6621,0.3023]
|
||||
brightness: 255
|
||||
light.couch_tv_light:
|
||||
state: on
|
||||
transition: 400
|
||||
xy_color: [0.6621,0.3023]
|
||||
brightness: 100
|
||||
|
||||
|
||||
# Restart Homebridge on HASS start
|
||||
# shell_command:
|
||||
# restart_homebridge: 'sudo su pi -c "pm2 restart homebridge"'
|
||||
# start_homebridge: 'sudo su pi -c "pm2 start homebridge"'
|
||||
# stop_homebridge: 'sudo su pi -c "pm2 stop homebridge"'
|
||||
|
||||
### Future Ideas
|
||||
|
||||
# - alias: 'Get Random Time'
|
||||
# trigger:
|
||||
# platform: time
|
||||
# after: '21:00:00'
|
||||
# action:
|
||||
# - service: input_slider.select_value
|
||||
# data_template:
|
||||
# entity_id: input_slider.hour
|
||||
# value: '{{ (range(22, 23) | random) }}'
|
||||
# - service: input_slider.select_value
|
||||
# data_template:
|
||||
# entity_id: input_slider.random_minute
|
||||
# value: '{{ (range(30, 45) | random) }}'
|
||||
# Then simply use that in your light turn off automation:
|
||||
|
||||
# - alias: 'Turn lights off'
|
||||
# trigger:
|
||||
# platform: template
|
||||
# value_template: '{{ now.hour == (states.input_slider.random_hour.state | int) and now.minute == (states.input_slider.random_minute.state | int) }}'
|
||||
# action:
|
||||
# - service: light.turn_off
|
||||
# data:
|
||||
# entity_id: light.hue_color_lamp_1
|
||||
|
||||
|
||||
#Todo List
|
||||
|
||||
# AUTOMATE LAMP UPSTAIRS USING ifttt AND Nest thermostat.
|
||||
# Put Dash Buttons out there.
|
||||
# Put door sensor on garage door
|
BIN
deps/.DS_Store
vendored
Normal file
BIN
deps/.DS_Store
vendored
Normal file
Binary file not shown.
3
deps/CherryPy-8.1.0.dist-info/DESCRIPTION.rst
vendored
Normal file
3
deps/CherryPy-8.1.0.dist-info/DESCRIPTION.rst
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
CherryPy is a pythonic, object-oriented HTTP framework
|
||||
|
||||
|
1
deps/CherryPy-8.1.0.dist-info/INSTALLER
vendored
Normal file
1
deps/CherryPy-8.1.0.dist-info/INSTALLER
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
44
deps/CherryPy-8.1.0.dist-info/METADATA
vendored
Normal file
44
deps/CherryPy-8.1.0.dist-info/METADATA
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: CherryPy
|
||||
Version: 8.1.0
|
||||
Summary: Object-Oriented HTTP framework
|
||||
Home-page: http://www.cherrypy.org
|
||||
Author: CherryPy Team
|
||||
Author-email: team@cherrypy.org
|
||||
License: BSD
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: Freely Distributable
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Framework :: CherryPy
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.1
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: Implementation
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: Jython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
|
||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||
Requires-Dist: six
|
||||
Provides-Extra: This section defines feature flags end-users can use in dependenciesmemcached-session
|
||||
Requires-Dist: python-memcached (>=1.58); extra == 'This section defines feature flags end-users can use in dependenciesmemcached-session'
|
||||
|
||||
CherryPy is a pythonic, object-oriented HTTP framework
|
||||
|
||||
|
242
deps/CherryPy-8.1.0.dist-info/RECORD
vendored
Normal file
242
deps/CherryPy-8.1.0.dist-info/RECORD
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
../../cherrypy/LICENSE.txt,sha256=IauyrgxBFcHp6BdR1ETnWowNhkQ8WGiakDvwZn4uMaw,1582
|
||||
../../cherrypy/cherryd,sha256=NHP2aVxBaufEZ-xOtagQyikGYospC2E53zelFcQG4l8,101
|
||||
../../cherrypy/favicon.ico,sha256=jrNK5SnKfbnSFgX_xQyLX3khmeImw8IbbHgJVIsGci0,1406
|
||||
../../cherrypy/scaffold/example.conf,sha256=UXZHQHu71WKWzCei-Olo1gUreX8AcoW5exkUSi3bVaI,61
|
||||
../../cherrypy/scaffold/site.conf,sha256=pjUhF-ir1xzSsV7LqXGfyR6Ns_r_n3ATWw8OlfbgT3w,426
|
||||
../../cherrypy/scaffold/static/made_with_cherrypy_small.png,sha256=-A_xqPsK-9k4-36Uy1Uw4s7ufbs9cm30gQ8fb90JscA,7455
|
||||
../../cherrypy/test/style.css,sha256=2Ypw_ziOWlY4dTZJlwsrorDLqLA1z485lgagaGemtKQ,17
|
||||
../../cherrypy/test/test.pem,sha256=x6LrLPw2dBRyZwHXk6FhdSDNM3-Cv7DBXc8o4A19RhI,2254
|
||||
../../cherrypy/test/static/dirback.jpg,sha256=naVXoM4eMycBnz1jxqlMLFVWkM9-DgFQRIYiCvjrCRo,18238
|
||||
../../cherrypy/test/static/index.html,sha256=cB6ALrLhcxEGyMNgOHzmnBvmRnPCW_u3ebZUqdCiHkQ,14
|
||||
../../cherrypy/tutorial/README.txt,sha256=6S48iS53EwxP16p7ttB0oDE0H6T_31WVhl5TQOnupuM,703
|
||||
../../cherrypy/tutorial/custom_error.html,sha256=9cMEb83zwct9i-fJlyl7yvBSNexF7yEIWOoxH8lpllQ,404
|
||||
../../cherrypy/tutorial/pdf_file.pdf,sha256=_4ED6K9omlqDodRtP1G3oagWwF4vqQbT1yjeA2UASgw,85698
|
||||
../../cherrypy/tutorial/tutorial.conf,sha256=9ENgfRDyopHuignr_aHeMaWoC562xThbmlgF6zg4oEE,96
|
||||
../../bin/cherryd,sha256=yoXgNxNxnyqQb35YPan1JBakxaktUYlclUaIsHsjOWA,87
|
||||
CherryPy-8.1.0.dist-info/DESCRIPTION.rst,sha256=-2ZR2c39k2rBpsSnTMQh6aAYchh9RnvriVjF0BmLbOQ,57
|
||||
CherryPy-8.1.0.dist-info/METADATA,sha256=yDpbX_a4VGrpOQM5-xdeQ8nVwx0shZbUCYXMoOAmKaE,2028
|
||||
CherryPy-8.1.0.dist-info/RECORD,,
|
||||
CherryPy-8.1.0.dist-info/WHEEL,sha256=rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g,92
|
||||
CherryPy-8.1.0.dist-info/metadata.json,sha256=pFeuWbZb0Q-cH1Q1w9Z9guuvR6ssDsrUWrQgHRgDxSc,1989
|
||||
CherryPy-8.1.0.dist-info/top_level.txt,sha256=mOBE-r7Ej1kFrXNKlOj3yY9QfGA6Xkz6vZK7VNJF3YE,9
|
||||
cherrypy/__init__.py,sha256=gFcXKfeonIhWgYdgTn2pzYkzYCy2EHuRjqIrKiAkAjw,11891
|
||||
cherrypy/__main__.py,sha256=RC6ZmBSxqBfas6Y_BBYduvoTM933wRjqW5g24awvUcg,77
|
||||
cherrypy/_cpchecker.py,sha256=EEVtXoJLtCgzh6N1xjX28MpTlvHwWlfK8hKscEPrp2Y,15034
|
||||
cherrypy/_cpcompat.py,sha256=as8wX_SQ8y95PNellWx2EuUTXhNb3ddM_qG5CYxfe0o,9716
|
||||
cherrypy/_cpconfig.py,sha256=77jqoLwfGIUwitNvCwrMb4ory0TbicTN7PwMbDEwYdE,10048
|
||||
cherrypy/_cpdispatch.py,sha256=4X7qD6ujNWZFJQp9fRnur12CZUt0-TjHtpF_9JRiyF4,25303
|
||||
cherrypy/_cperror.py,sha256=7eqrhVsyurxGPLh-PcKj187zkccJ6d-7qLYYo8SYCZk,22963
|
||||
cherrypy/_cplogging.py,sha256=DJRDGJt1XZH9eSwPud2b30qdLyPwD5-VYMmbcFR47jg,17211
|
||||
cherrypy/_cpmodpy.py,sha256=M2RpPP4MUSVT1n5GXsRi4yjsDB4AR-IOPJfp1W8x3fE,11158
|
||||
cherrypy/_cpnative_server.py,sha256=919Jn2tGn2kpZhE0zU_S00oqcAnUeu1dey1UJJ9FOZA,5974
|
||||
cherrypy/_cpreqbody.py,sha256=LbcCYkIp3VZeuzywySGtTLtlxv9ktI2BEN9VTb06xAE,37427
|
||||
cherrypy/_cprequest.py,sha256=BXWubSLvtvRyTTLpj-IhoYl5-d_7fMMfmBnooGxHHvw,37198
|
||||
cherrypy/_cpserver.py,sha256=TJ4KXCBeJBfXc5_kte1JkEutHKCIZnP707dtdu3FgGY,8277
|
||||
cherrypy/_cpthreadinglocal.py,sha256=kZGe956xtkv9Gp6bkZxYuqUy_MY4WCGSuLHFFWkXWGE,6619
|
||||
cherrypy/_cptools.py,sha256=RbMUwU13wdZoQB-R4KqsqaHfoo3e3YmE0nT3fO1Q17s,19426
|
||||
cherrypy/_cptree.py,sha256=sbkOdp4uLIjEQrZeCC0wpHV3F-LV2uu7uRMRfDBlZzo,10394
|
||||
cherrypy/_cpwsgi.py,sha256=bMHcT8USUtY8L5ReaNhvk2tqqS1o3h8nBngfcDNOZVE,16864
|
||||
cherrypy/_cpwsgi_server.py,sha256=qcsgvqm2FjHOzu1N7DSzMk16LlseAM2WFPFVunbW_nA,3023
|
||||
cherrypy/_helper.py,sha256=N0lfUrPU1RdfxXIYDpHccZ2LQAM3rNiJnrdxWDM7MSU,10338
|
||||
cherrypy/daemon.py,sha256=dqtbnWyMZUrbvLfZItQT5aPyAAWOqmfv6K0_Q_VzSco,3913
|
||||
cherrypy/lib/__init__.py,sha256=WeVRLlnimPVlecYFaOawcnlpPYVrkucteVKeHUoBYvY,2393
|
||||
cherrypy/lib/auth.py,sha256=WA8TpNvWSoPAL3din7TVceR41uHJj6ZuaryNUEl081c,3224
|
||||
cherrypy/lib/auth_basic.py,sha256=vYLRPP97S9jkNVGDeKDyU2lKt8yh-WoizcZu_QlQ_BY,3401
|
||||
cherrypy/lib/auth_digest.py,sha256=mNRIiN9-QIuGVWqIQC-cUpbVWmdXdeyMASLaYsCGrR0,14178
|
||||
cherrypy/lib/caching.py,sha256=DWovFBAwBmPPAi9jIevTEhvdrAz43uKoW27m7mkdTOI,17147
|
||||
cherrypy/lib/covercp.py,sha256=LST3y1SNuVT9v14WMSIAGYerwFxAkVy65SxW9FRm1vI,11598
|
||||
cherrypy/lib/cpstats.py,sha256=8mM-x5n3l604kLOHsm7bd3RtTtJaoeuYQo5twQoKG9A,23042
|
||||
cherrypy/lib/cptools.py,sha256=o_dyD3_RKqy_Gp1zRZktmPi_HiGYqsanwWmMybduxxk,23756
|
||||
cherrypy/lib/encoding.py,sha256=Bydtgx6mAtQzWD_9zEP90-fWUznd6-NllxoWmntJcg0,16240
|
||||
cherrypy/lib/gctools.py,sha256=Rxk7SHrHuE1TMoJh48Q5NOmSmc_p8oPeVbuWWfmhayo,7344
|
||||
cherrypy/lib/httpauth.py,sha256=qcah2nyHpBshXgwrcpf3FErZbpBp1L3hziYKbV5VlKk,13050
|
||||
cherrypy/lib/httputil.py,sha256=noiOj5nW17Ol0RNumkcNO4fIzBw7tDd2Is-fmvIKpsg,17603
|
||||
cherrypy/lib/jsontools.py,sha256=Jk79XKP9HZavy7LiEEUXKyg8UYywTnE5c7pvxhb6MLA,3924
|
||||
cherrypy/lib/lockfile.py,sha256=xEt_WOr1y7fc2eG2Fj_yAzyRYJi6gUXPNzZpHVfEsSI,3448
|
||||
cherrypy/lib/locking.py,sha256=H80gHcrHTret5i_29c7ISzf3_GyaNw2QsicDQbwo2iE,1224
|
||||
cherrypy/lib/profiler.py,sha256=GsRoaZW_syTg4xlHZjNd_Od5xppoZRvDAAlkenxSDB8,6489
|
||||
cherrypy/lib/reprconf.py,sha256=x9iYaaEXqrj-fqNSCJ5HDyyiPZqEq3S6va2GFrSFeAM,16186
|
||||
cherrypy/lib/sessions.py,sha256=QHpfijremktJYm7MIm5UmjXe6TUGFoS62RW7dU8v0hw,30438
|
||||
cherrypy/lib/static.py,sha256=8dc32u1c5JJ82WLG1iinS3hpxfM2QMln6Grv7Oa88E8,14960
|
||||
cherrypy/lib/xmlrpcutil.py,sha256=QyCVvWReaqs6ZcnqgPfuLIYQyMUfWI2k2w5PMnPxUCw,1608
|
||||
cherrypy/process/__init__.py,sha256=_kdPC_Wa66SIY6S4L14mZXSUFHPJK2q79bp6vyS9udk,552
|
||||
cherrypy/process/plugins.py,sha256=CXQE3fvNM1d5vehVOH1i6se6PblXqo48JSn-LdFHmmY,26654
|
||||
cherrypy/process/servers.py,sha256=9UyrJ___5fJfyoxoqEpGf8p_vnGPtm0B_VWpr8xJbDA,15727
|
||||
cherrypy/process/win32.py,sha256=VIilNKDJrGuF4v1u5Ki8soLarKUGiHUdhVI5f-70QBs,5772
|
||||
cherrypy/process/wspbus.py,sha256=JgrVeCzKq0MNajC9xW3zoyCHbZsTJrWO6ncZ7VTviT0,17156
|
||||
cherrypy/scaffold/__init__.py,sha256=XIb5M6FokgALqxAbrH2vxquWpKJs1xm4Q85ptJ6XK20,1762
|
||||
cherrypy/test/__init__.py,sha256=7vswU9gMLQpcAP01bgFC4TCmgfyUe5Z6cGW2POImxeM,692
|
||||
cherrypy/test/_test_decorators.py,sha256=HxU9-DiUdrhfzxa2XOZQV2b5suCI6e18hqs15QBXhh4,992
|
||||
cherrypy/test/_test_states_demo.py,sha256=8GC746zLbqPQCj6AplYSCa_kCkDDPyh3Tvuwi-Tk3jM,1925
|
||||
cherrypy/test/benchmark.py,sha256=S7khs-SMv3KrD6e3LtJplxU4ROp5E_Kf9kfHnputr6E,12536
|
||||
cherrypy/test/checkerdemo.py,sha256=VpLy7yTyqt8n3NErv4vFfn7q5xrhSghq0x3AzYT0VIY,1860
|
||||
cherrypy/test/helper.py,sha256=E4DilAaMRaVO8ghNBD_Jcm8rFtjPTiRFkVW1lhM8pww,17573
|
||||
cherrypy/test/logtest.py,sha256=AnFPL11lFnSlx82xEzMVedalTVO_-wrmRrX06AX83XU,7097
|
||||
cherrypy/test/modfastcgi.py,sha256=2T3R9FCcsHVcssHfl3zRon_sQjpEEv_aRR-ZUy8Q-O4,4651
|
||||
cherrypy/test/modfcgid.py,sha256=rX57myEw4HmRnSmeiYWnn9M4Gwp1DUie8NGyRzFHoBc,4236
|
||||
cherrypy/test/modpy.py,sha256=0UxLfvq81J-0awar-jTZNV1mUv0wFOlWK7A-MI4IkQo,5009
|
||||
cherrypy/test/modwsgi.py,sha256=COV2DMPmlLtpQ4TGIXLqy4bAqtHbonhhNrffXBScybI,4832
|
||||
cherrypy/test/sessiondemo.py,sha256=GJCcNPb_-a_1dush4vGOotP7WjfPwweA4kJ6LWzyqio,5447
|
||||
cherrypy/test/test_auth_basic.py,sha256=SCznMKX1dZaz_az_vs2381WV9vsYzRGJ-JgEBL8vyqg,3039
|
||||
cherrypy/test/test_auth_digest.py,sha256=xipRY65noqfIuSeayk33xOZHCjhkmbH_ajtzhXnHZSE,5066
|
||||
cherrypy/test/test_bus.py,sha256=sZmIGf693GPTqQFzNp4R4qLBU5iJa9GrUnicR9zA2hQ,9007
|
||||
cherrypy/test/test_caching.py,sha256=IqiwMX3vlP6GPxlBcAKGWTkiOP59KKgNuQzpz-LqXb0,12587
|
||||
cherrypy/test/test_compat.py,sha256=CEAwfFJjK6bXDnRmGlj-vhWeHH4Nl63YRZ8yhRuIZ68,474
|
||||
cherrypy/test/test_config.py,sha256=1lcGqzylyBma8Itt3yML2XcZWlpeI3sOAR2g9XAEBiU,9179
|
||||
cherrypy/test/test_config_server.py,sha256=V26tL8mpRmzG2ZosatDGiRzo47vp4MbbW6toIQzaZLI,4036
|
||||
cherrypy/test/test_conn.py,sha256=CJK0RXKG2dw4ai9i29jzY-J0Sc9eHVax6L3MTTE425Y,30953
|
||||
cherrypy/test/test_core.py,sha256=aZeMYYyl3YYfQWqWBAwgMk1Hp2pkz_izHtwVE9BWL9M,28039
|
||||
cherrypy/test/test_dynamicobjectmapping.py,sha256=yTqw_mrm8Jzzsa1UCEUfZ-V5_gI4Up9vwdhgcwhmld8,12393
|
||||
cherrypy/test/test_encoding.py,sha256=3Np9WtdKUNfLl6kIePeHlJrRCAeWkRv0QwCBJAnTo_4,16647
|
||||
cherrypy/test/test_etags.py,sha256=VEa5oA1zD3PqYKfLzFYXNHtnb0IzXOqOlY-AGXMp_Sg,3093
|
||||
cherrypy/test/test_http.py,sha256=b3bdpK49xufkwCVQFtAvZ4-HATDAlRIDpM9sucx3Jr8,9933
|
||||
cherrypy/test/test_httpauth.py,sha256=1fKirgAkRcrluHzmWCrsr70RoHpaa6bCIIkiVODGuZI,6313
|
||||
cherrypy/test/test_httplib.py,sha256=LHTbWmiglY1qW_40I_pLVcrmqeOxiq2ww6nTb9anTKU,1287
|
||||
cherrypy/test/test_iterator.py,sha256=oi3Ja8BCNMC6s7cIYK1bbQLXZvmriwG2yR-vjERIYk0,5709
|
||||
cherrypy/test/test_json.py,sha256=8-r2dIvQ_6IHADT6bA4pGMw6-unRWg998AXIKrNmcQs,2864
|
||||
cherrypy/test/test_logging.py,sha256=4leoBQTGz7_75oHzch5vBwLB4jDu_GcGDypIMlSIzXM,6053
|
||||
cherrypy/test/test_mime.py,sha256=37gS3iX0PcHLdm-EuPO01ZkyeFwlwiRsnbin9JpVnbw,4602
|
||||
cherrypy/test/test_misc_tools.py,sha256=AbeWO7rznVs839Jhp6VWBDuzFkvgbZ7xAP8CZQcD_W4,7453
|
||||
cherrypy/test/test_objectmapping.py,sha256=qiP1zq2BmG00HEAYpJzEsP9aZ-7KO0TnIa4sP-Tw20k,14521
|
||||
cherrypy/test/test_params.py,sha256=M17ptv_JX0yQAwgE98cCwhXBZhllh1zCPgPQo_x_Cu8,1819
|
||||
cherrypy/test/test_proxy.py,sha256=qb1Tp5cQfidZXgoDJ5DJEQGktvjJdFdwOmxhIwYsf2g,5062
|
||||
cherrypy/test/test_refleaks.py,sha256=1qsVAJ4AOB8ma7BWpY9siGwpTUXG_b-CO-8K_udNpmg,1535
|
||||
cherrypy/test/test_request_obj.py,sha256=gX2TwPC1pCfFZfb8Tv49g9dXWtK1XOuuQcv_UBK53Bk,32339
|
||||
cherrypy/test/test_routes.py,sha256=63-JJS5PSwamgXHat1QKkuNKLINaLAvZc3hM8F4hn4M,2366
|
||||
cherrypy/test/test_session.py,sha256=jWUj6Xm_UxRUNKIZe3afqCLzc3AofJzKpsfZjrevysU,17150
|
||||
cherrypy/test/test_sessionauthenticate.py,sha256=Jcp9mjXMiAhMjS8NKEKtkkH0bFm6LTS6zUGHc31-hnM,2013
|
||||
cherrypy/test/test_states.py,sha256=9Iz-lVx27Sj0oXo3LlmvLXz5EwoLhfMZvhJybqRSWTY,19091
|
||||
cherrypy/test/test_static.py,sha256=rBbKTUrKIB7VBoLeTCXzfeGEaDb-85ky9h8G0kJT-zM,14051
|
||||
cherrypy/test/test_tools.py,sha256=gi-N2WYNkXvv57VaYH8XVo0IyRbQFHCcO-4Fq6h0GZU,16899
|
||||
cherrypy/test/test_tutorials.py,sha256=jq9-AMO0UicQ35IMERyrg22uYCR-oQhXPq8BUlpRzK4,7239
|
||||
cherrypy/test/test_virtualhost.py,sha256=WDz1rD69PwRiGh6xNResy36o_ILDgBFlNZAcVyR5l_c,4020
|
||||
cherrypy/test/test_wsgi_ns.py,sha256=2SLVbU4Fe1Znt1AC561fh06BM-FGpZGRcEPa3v4eARU,2868
|
||||
cherrypy/test/test_wsgi_unix_socket.py,sha256=nkCl6LEohAI186awz65weREZ0o9BgKRdpDMaTVsMv6A,2635
|
||||
cherrypy/test/test_wsgi_vhost.py,sha256=Wsi0bibrBUhJBOkPzuxy4-SELbMLpk7Hl6P_oND8Xfo,1034
|
||||
cherrypy/test/test_wsgiapps.py,sha256=IatsMtVjrsHPBuM4e5nAGMZcdcs2sxJSxyDN_xJ6Usg,4190
|
||||
cherrypy/test/test_xmlrpc.py,sha256=2fz5sFBwkW2HRPsrcxJ7gkTRrMJzQtG71ZhR-0ukPrk,5830
|
||||
cherrypy/test/webtest.py,sha256=_JjzK2J8dLdIlvQwdh1rBMCIkUJpDgfMpiHTMKRGqgo,20399
|
||||
cherrypy/tutorial/__init__.py,sha256=cmLXfvQI0L6trCXwDzR0WE1bu4JZYt301HJRNhjZOBc,85
|
||||
cherrypy/tutorial/tut01_helloworld.py,sha256=MRt9GwP9IoUcXOG4Cq1-Asge1ToX1kvTjpLY5U-5VyI,1014
|
||||
cherrypy/tutorial/tut02_expose_methods.py,sha256=kYSTvBRXBDE-uQekMW66rVTRaVC2rS3UySyEi_KSNS8,799
|
||||
cherrypy/tutorial/tut03_get_and_post.py,sha256=VTXZfGr7KRzo-NXG713RsHh4FEqxjoS3agzoy15Ksuo,1586
|
||||
cherrypy/tutorial/tut04_complex_site.py,sha256=TZSRXINZVJIgTPnWtH4uunwjROZIid0QWLxNcpoySM4,2947
|
||||
cherrypy/tutorial/tut05_derived_objects.py,sha256=N9w8D2ohznvBbPdWb5OhkQLcidDCiLuMBMtJIX8ofp0,2140
|
||||
cherrypy/tutorial/tut06_default_method.py,sha256=AcIdYCpd08czHL7VdeCI_k4t7dALobxledk0ss8RHPk,2263
|
||||
cherrypy/tutorial/tut07_sessions.py,sha256=jSoXprvwLUxvR7QiuyCD0bHLDVYRlJk4mpNWTYnqrWU,1227
|
||||
cherrypy/tutorial/tut08_generators_and_yield.py,sha256=GWIWPhkQ9v5v6ReNjoZhPvnoK6H0_mUw1vut87WWoHE,1287
|
||||
cherrypy/tutorial/tut09_files.py,sha256=tTk_CCgQrv8xWr-L-YgUx5mQD6HELYvfJ3uBhtY_wIQ,3462
|
||||
cherrypy/tutorial/tut10_http_errors.py,sha256=NWCe8DJrNLRghNUdTRB1Ge_2dBvgYBCyv8QGNzpfHw4,2705
|
||||
cherrypy/wsgiserver/__init__.py,sha256=Hf2IefJVSLv45C8j7-JwrQS_CGP0_NTAZQ3WdXMQTmI,92609
|
||||
cherrypy/wsgiserver/ssl_builtin.py,sha256=MCCgEbSg1SVmi4AaYyghoSBqT-q99YxZcK9M5_D6D8A,3833
|
||||
CherryPy-8.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
cherrypy/test/__pycache__/benchmark.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_json.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_config_server.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_httpauth.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_misc_tools.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/helper.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/reprconf.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_auth_basic.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/auth_digest.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/__main__.cpython-34.pyc,,
|
||||
cherrypy/process/__pycache__/plugins.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_helper.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut03_get_and_post.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/jsontools.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_conn.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cptools.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/daemon.cpython-34.pyc,,
|
||||
cherrypy/wsgiserver/__pycache__/ssl_builtin.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_xmlrpc.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/sessiondemo.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_caching.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cptree.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpreqbody.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/static.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/lockfile.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cplogging.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cprequest.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_proxy.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut02_expose_methods.cpython-34.pyc,,
|
||||
cherrypy/process/__pycache__/servers.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_auth_digest.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut08_generators_and_yield.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/modpy.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/locking.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/logtest.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cperror.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/_test_states_demo.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_mime.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_core.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_objectmapping.cpython-34.pyc,,
|
||||
cherrypy/scaffold/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut10_http_errors.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/modfastcgi.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_dynamicobjectmapping.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_states.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpnative_server.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut07_sessions.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_params.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_tutorials.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_session.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_tools.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/auth.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/sessions.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_wsgi_ns.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpmodpy.cpython-34.pyc,,
|
||||
cherrypy/process/__pycache__/wspbus.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/httputil.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_httplib.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_static.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/xmlrpcutil.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_config.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_routes.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpwsgi_server.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpconfig.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_logging.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut05_derived_objects.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/checkerdemo.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpcompat.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut06_default_method.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_encoding.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_wsgi_unix_socket.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/modfcgid.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_request_obj.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_bus.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/auth_basic.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/covercp.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_refleaks.cpython-34.pyc,,
|
||||
cherrypy/wsgiserver/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_wsgi_vhost.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/modwsgi.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_etags.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_compat.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_wsgiapps.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_http.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/gctools.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/encoding.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpwsgi.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_iterator.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/profiler.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/cpstats.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut09_files.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut04_complex_site.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/tut01_helloworld.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/process/__pycache__/win32.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpthreadinglocal.cpython-34.pyc,,
|
||||
cherrypy/tutorial/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/cptools.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_sessionauthenticate.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/_test_decorators.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpdispatch.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpchecker.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/httpauth.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/webtest.cpython-34.pyc,,
|
||||
cherrypy/lib/__pycache__/caching.cpython-34.pyc,,
|
||||
cherrypy/process/__pycache__/__init__.cpython-34.pyc,,
|
||||
cherrypy/__pycache__/_cpserver.cpython-34.pyc,,
|
||||
cherrypy/test/__pycache__/test_virtualhost.cpython-34.pyc,,
|
5
deps/CherryPy-8.1.0.dist-info/WHEEL
vendored
Normal file
5
deps/CherryPy-8.1.0.dist-info/WHEEL
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
1
deps/CherryPy-8.1.0.dist-info/metadata.json
vendored
Normal file
1
deps/CherryPy-8.1.0.dist-info/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: Freely Distributable", "Operating System :: OS Independent", "Framework :: CherryPy", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: Jython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "extensions": {"python.details": {"contacts": [{"email": "team@cherrypy.org", "name": "CherryPy Team", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://www.cherrypy.org"}}}, "extras": ["This section defines feature flags end-users can use in dependenciesmemcached-session"], "generator": "bdist_wheel (0.29.0)", "license": "BSD", "metadata_version": "2.0", "name": "CherryPy", "run_requires": [{"extra": "This section defines feature flags end-users can use in dependenciesmemcached-session", "requires": ["python-memcached (>=1.58)"]}, {"requires": ["six"]}], "summary": "Object-Oriented HTTP framework", "test_requires": [{"requires": ["tox"]}], "version": "8.1.0"}
|
1
deps/CherryPy-8.1.0.dist-info/top_level.txt
vendored
Normal file
1
deps/CherryPy-8.1.0.dist-info/top_level.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
cherrypy
|
160
deps/PyChromecast-0.7.4.dist-info/DESCRIPTION.rst
vendored
Normal file
160
deps/PyChromecast-0.7.4.dist-info/DESCRIPTION.rst
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
pychromecast |Build Status|
|
||||
===========================
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.org/balloob/pychromecast.svg?branch=master
|
||||
:target: https://travis-ci.org/balloob/pychromecast
|
||||
|
||||
Library for Python 2 and 3 to communicate with the Google Chromecast. It
|
||||
currently supports:
|
||||
|
||||
- Auto discovering connected Chromecasts on the network
|
||||
- Start the default media receiver and play any online media
|
||||
- Control playback of current playing media
|
||||
- Implement Google Chromecast api v2
|
||||
- Communicate with apps via channels
|
||||
- Easily extendable to add support for unsupported namespaces
|
||||
- Multi-room setups with Audio cast devices
|
||||
|
||||
*PyChromecast 0.6 introduces some backward incompatible changes due to
|
||||
the migration from DIAL to socket for retrieving the app status.*
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
PyChromecast depends on the Python packages requests, protobuf and
|
||||
zeroconf. Make sure you have these dependencies installed using
|
||||
``pip install -r requirements.txt``
|
||||
|
||||
Some users running Python 2.7 have `reported`_ that they had to upgrade
|
||||
their version of pip using ``pip install --upgrade pip`` before they
|
||||
were able to install the latest version of the dependencies.
|
||||
|
||||
.. _reported: https://github.com/balloob/pychromecast/issues/47#issuecomment-107822162
|
||||
|
||||
How to use
|
||||
----------
|
||||
|
||||
.. code:: python
|
||||
|
||||
>> from __future__ import print_function
|
||||
>> import time
|
||||
>> import pychromecast
|
||||
|
||||
>> pychromecast.get_chromecasts_as_dict().keys()
|
||||
['Dev', 'Living Room', 'Den', 'Bedroom']
|
||||
|
||||
>> cast = pychromecast.get_chromecast(friendly_name="Living Room")
|
||||
>> # Wait for cast device to be ready
|
||||
>> cast.wait()
|
||||
>> print(cast.device)
|
||||
DeviceStatus(friendly_name='Living Room', model_name='Chromecast', manufacturer='Google Inc.', api_version=(1, 0), uuid=UUID('df6944da-f016-4cb8-97d0-3da2ccaa380b'), cast_type='cast')
|
||||
|
||||
>> print(cast.status)
|
||||
CastStatus(is_active_input=True, is_stand_by=False, volume_level=1.0, volume_muted=False, app_id=u'CC1AD845', display_name=u'Default Media Receiver', namespaces=[u'urn:x-cast:com.google.cast.player.message', u'urn:x-cast:com.google.cast.media'], session_id=u'CCA39713-9A4F-34A6-A8BF-5D97BE7ECA5C', transport_id=u'web-9', status_text='')
|
||||
|
||||
>> mc = cast.media_controller
|
||||
>> mc.play_media('http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', 'video/mp4')
|
||||
>> print(mc.status)
|
||||
MediaStatus(current_time=42.458322, content_id=u'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', content_type=u'video/mp4', duration=596.474195, stream_type=u'BUFFERED', idle_reason=None, media_session_id=1, playback_rate=1, player_state=u'PLAYING', supported_media_commands=15, volume_level=1, volume_muted=False)
|
||||
|
||||
>> mc.pause()
|
||||
>> time.sleep(5)
|
||||
>> mc.play()
|
||||
|
||||
Adding support for extra namespaces
|
||||
-----------------------------------
|
||||
|
||||
Each app that runs on the Chromecast supports namespaces. They specify a
|
||||
JSON-based mini-protocol. This is used to communicate between the
|
||||
Chromecast and your phone/browser and now Python.
|
||||
|
||||
Support for extra namespaces is added by using controllers. To add your own namespace to a current chromecast instance you will first have to define your controller. Example of a minimal controller:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from pychromecast.controllers import BaseController
|
||||
|
||||
class MyController(BaseController):
|
||||
def __init__(self):
|
||||
super(MyController, self).__init__(
|
||||
"urn:x-cast:my.super.awesome.namespace")
|
||||
|
||||
def receive_message(self, message, data):
|
||||
print("Wow, I received this message: {}".format(data))
|
||||
|
||||
return True # indicate you handled this message
|
||||
|
||||
def request_beer(self):
|
||||
self.send_message({'request': 'beer'})
|
||||
|
||||
After you have defined your controller you will have to add an instance to a Chromecast object: `cast.register_handler(MyController())`. When a message is received with your namespace it will be routed to your controller.
|
||||
|
||||
For more options see the `BaseController`_. For an example of a fully implemented controller see the `MediaController`_.
|
||||
|
||||
.. _BaseController: https://github.com/balloob/pychromecast/blob/master/pychromecast/controllers/__init__.py
|
||||
.. _MediaController: https://github.com/balloob/pychromecast/blob/master/pychromecast/controllers/media.py
|
||||
|
||||
Exploring existing namespaces
|
||||
-------------------------------
|
||||
So you've got PyChromecast running and decided it is time to add support to your favorite app. No worries, the following instructions will have you covered in exploring the possibilities.
|
||||
|
||||
The following instructions require the use of the `Google Chrome browser`_ and the `Google Cast plugin`_.
|
||||
|
||||
* In Chrome, go to `chrome://net-internals/#capture`
|
||||
* Enable the checkbox 'Include the actual bytes sent/received.'
|
||||
* Open a new tab, browse to your favorite application on the web that has Chromecast support and start casting.
|
||||
* Go back to the tab that is capturing events and click on stop.
|
||||
* From the dropdown click on events. This will show you a table with events that happened while you were recording.
|
||||
* In the filter box enter the text `Tr@n$p0rt`. This should give one SOCKET connection as result: the connection with your Chromecast.
|
||||
* Go through the results and collect the JSON that is exchanged.
|
||||
* Now write a controller that is able to mimic this behavior :-)
|
||||
|
||||
.. _Google Chrome Browser: https://www.google.com/chrome/
|
||||
.. _Google Cast Plugin: https://chrome.google.com/webstore/detail/google-cast/boadgeojelhgndaghljhdicfkmllpafd
|
||||
|
||||
Ignoring CEC Data
|
||||
-----------------
|
||||
The Chromecast typically reports whether it is the active input on the device
|
||||
to which it is connected. This value is stored inside a cast object in the
|
||||
following property.
|
||||
|
||||
.. code:: python
|
||||
|
||||
cast.status.is_active_input
|
||||
|
||||
Some Chromecast users have reported CEC incompatibilities with their media
|
||||
center devices. These incompatibilities may sometimes cause this active input
|
||||
value to be reported improperly.
|
||||
|
||||
This active input value is typically used to determine if the Chromecast
|
||||
is idle. PyChromecast is capable of ignoring the active input value when
|
||||
determining if the Chromecast is idle in the instance that the
|
||||
Chromecast is returning erroneous values. To ignore this CEC detection
|
||||
data in PyChromecast, append a `Linux style wildcard`_ formatted string
|
||||
to the IGNORE\_CEC list in PyChromecast like in the example below.
|
||||
|
||||
.. code:: python
|
||||
|
||||
pychromecast.IGNORE_CEC.append('*') # Ignore CEC on all devices
|
||||
pychromecast.IGNORE_CEC.append('Living Room') # Ignore CEC on Chromecasts named Living Room
|
||||
|
||||
Maintainers
|
||||
-----------
|
||||
|
||||
- Jan Borsodi (`@am0s`_)
|
||||
- Ryan Kraus (`@rmkraus`_)
|
||||
- Paulus Schoutsen (`@balloob`_, original author)
|
||||
|
||||
Thanks
|
||||
------
|
||||
|
||||
I would like to thank `Fred Clift`_ for laying the socket client ground
|
||||
work. Without him it would not have been possible!
|
||||
|
||||
.. _Linux style wildcard: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
|
||||
.. _@am0s: https://github.com/am0s
|
||||
.. _@rmkraus: https://github.com/rmkraus
|
||||
.. _@balloob: https://github.com/balloob
|
||||
.. _Fred Clift: https://github.com/minektur
|
||||
|
||||
|
1
deps/PyChromecast-0.7.4.dist-info/INSTALLER
vendored
Normal file
1
deps/PyChromecast-0.7.4.dist-info/INSTALLER
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
181
deps/PyChromecast-0.7.4.dist-info/METADATA
vendored
Normal file
181
deps/PyChromecast-0.7.4.dist-info/METADATA
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: PyChromecast
|
||||
Version: 0.7.4
|
||||
Summary: Python module to talk to Google Chromecast.
|
||||
Home-page: https://github.com/balloob/pychromecast
|
||||
Author: Paulus Schoutsen
|
||||
Author-email: paulus@paulusschoutsen.nl
|
||||
License: MIT
|
||||
Platform: any
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Dist: protobuf (==3.0.0b2)
|
||||
Requires-Dist: requests (>=2.0)
|
||||
Requires-Dist: six (>=1.10.0)
|
||||
Requires-Dist: zeroconf (>=0.17.4)
|
||||
|
||||
pychromecast |Build Status|
|
||||
===========================
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.org/balloob/pychromecast.svg?branch=master
|
||||
:target: https://travis-ci.org/balloob/pychromecast
|
||||
|
||||
Library for Python 2 and 3 to communicate with the Google Chromecast. It
|
||||
currently supports:
|
||||
|
||||
- Auto discovering connected Chromecasts on the network
|
||||
- Start the default media receiver and play any online media
|
||||
- Control playback of current playing media
|
||||
- Implement Google Chromecast api v2
|
||||
- Communicate with apps via channels
|
||||
- Easily extendable to add support for unsupported namespaces
|
||||
- Multi-room setups with Audio cast devices
|
||||
|
||||
*PyChromecast 0.6 introduces some backward incompatible changes due to
|
||||
the migration from DIAL to socket for retrieving the app status.*
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
PyChromecast depends on the Python packages requests, protobuf and
|
||||
zeroconf. Make sure you have these dependencies installed using
|
||||
``pip install -r requirements.txt``
|
||||
|
||||
Some users running Python 2.7 have `reported`_ that they had to upgrade
|
||||
their version of pip using ``pip install --upgrade pip`` before they
|
||||
were able to install the latest version of the dependencies.
|
||||
|
||||
.. _reported: https://github.com/balloob/pychromecast/issues/47#issuecomment-107822162
|
||||
|
||||
How to use
|
||||
----------
|
||||
|
||||
.. code:: python
|
||||
|
||||
>> from __future__ import print_function
|
||||
>> import time
|
||||
>> import pychromecast
|
||||
|
||||
>> pychromecast.get_chromecasts_as_dict().keys()
|
||||
['Dev', 'Living Room', 'Den', 'Bedroom']
|
||||
|
||||
>> cast = pychromecast.get_chromecast(friendly_name="Living Room")
|
||||
>> # Wait for cast device to be ready
|
||||
>> cast.wait()
|
||||
>> print(cast.device)
|
||||
DeviceStatus(friendly_name='Living Room', model_name='Chromecast', manufacturer='Google Inc.', api_version=(1, 0), uuid=UUID('df6944da-f016-4cb8-97d0-3da2ccaa380b'), cast_type='cast')
|
||||
|
||||
>> print(cast.status)
|
||||
CastStatus(is_active_input=True, is_stand_by=False, volume_level=1.0, volume_muted=False, app_id=u'CC1AD845', display_name=u'Default Media Receiver', namespaces=[u'urn:x-cast:com.google.cast.player.message', u'urn:x-cast:com.google.cast.media'], session_id=u'CCA39713-9A4F-34A6-A8BF-5D97BE7ECA5C', transport_id=u'web-9', status_text='')
|
||||
|
||||
>> mc = cast.media_controller
|
||||
>> mc.play_media('http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', 'video/mp4')
|
||||
>> print(mc.status)
|
||||
MediaStatus(current_time=42.458322, content_id=u'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', content_type=u'video/mp4', duration=596.474195, stream_type=u'BUFFERED', idle_reason=None, media_session_id=1, playback_rate=1, player_state=u'PLAYING', supported_media_commands=15, volume_level=1, volume_muted=False)
|
||||
|
||||
>> mc.pause()
|
||||
>> time.sleep(5)
|
||||
>> mc.play()
|
||||
|
||||
Adding support for extra namespaces
|
||||
-----------------------------------
|
||||
|
||||
Each app that runs on the Chromecast supports namespaces. They specify a
|
||||
JSON-based mini-protocol. This is used to communicate between the
|
||||
Chromecast and your phone/browser and now Python.
|
||||
|
||||
Support for extra namespaces is added by using controllers. To add your own namespace to a current chromecast instance you will first have to define your controller. Example of a minimal controller:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from pychromecast.controllers import BaseController
|
||||
|
||||
class MyController(BaseController):
|
||||
def __init__(self):
|
||||
super(MyController, self).__init__(
|
||||
"urn:x-cast:my.super.awesome.namespace")
|
||||
|
||||
def receive_message(self, message, data):
|
||||
print("Wow, I received this message: {}".format(data))
|
||||
|
||||
return True # indicate you handled this message
|
||||
|
||||
def request_beer(self):
|
||||
self.send_message({'request': 'beer'})
|
||||
|
||||
After you have defined your controller you will have to add an instance to a Chromecast object: `cast.register_handler(MyController())`. When a message is received with your namespace it will be routed to your controller.
|
||||
|
||||
For more options see the `BaseController`_. For an example of a fully implemented controller see the `MediaController`_.
|
||||
|
||||
.. _BaseController: https://github.com/balloob/pychromecast/blob/master/pychromecast/controllers/__init__.py
|
||||
.. _MediaController: https://github.com/balloob/pychromecast/blob/master/pychromecast/controllers/media.py
|
||||
|
||||
Exploring existing namespaces
|
||||
-------------------------------
|
||||
So you've got PyChromecast running and decided it is time to add support to your favorite app. No worries, the following instructions will have you covered in exploring the possibilities.
|
||||
|
||||
The following instructions require the use of the `Google Chrome browser`_ and the `Google Cast plugin`_.
|
||||
|
||||
* In Chrome, go to `chrome://net-internals/#capture`
|
||||
* Enable the checkbox 'Include the actual bytes sent/received.'
|
||||
* Open a new tab, browse to your favorite application on the web that has Chromecast support and start casting.
|
||||
* Go back to the tab that is capturing events and click on stop.
|
||||
* From the dropdown click on events. This will show you a table with events that happened while you were recording.
|
||||
* In the filter box enter the text `Tr@n$p0rt`. This should give one SOCKET connection as result: the connection with your Chromecast.
|
||||
* Go through the results and collect the JSON that is exchanged.
|
||||
* Now write a controller that is able to mimic this behavior :-)
|
||||
|
||||
.. _Google Chrome Browser: https://www.google.com/chrome/
|
||||
.. _Google Cast Plugin: https://chrome.google.com/webstore/detail/google-cast/boadgeojelhgndaghljhdicfkmllpafd
|
||||
|
||||
Ignoring CEC Data
|
||||
-----------------
|
||||
The Chromecast typically reports whether it is the active input on the device
|
||||
to which it is connected. This value is stored inside a cast object in the
|
||||
following property.
|
||||
|
||||
.. code:: python
|
||||
|
||||
cast.status.is_active_input
|
||||
|
||||
Some Chromecast users have reported CEC incompatibilities with their media
|
||||
center devices. These incompatibilities may sometimes cause this active input
|
||||
value to be reported improperly.
|
||||
|
||||
This active input value is typically used to determine if the Chromecast
|
||||
is idle. PyChromecast is capable of ignoring the active input value when
|
||||
determining if the Chromecast is idle in the instance that the
|
||||
Chromecast is returning erroneous values. To ignore this CEC detection
|
||||
data in PyChromecast, append a `Linux style wildcard`_ formatted string
|
||||
to the IGNORE\_CEC list in PyChromecast like in the example below.
|
||||
|
||||
.. code:: python
|
||||
|
||||
pychromecast.IGNORE_CEC.append('*') # Ignore CEC on all devices
|
||||
pychromecast.IGNORE_CEC.append('Living Room') # Ignore CEC on Chromecasts named Living Room
|
||||
|
||||
Maintainers
|
||||
-----------
|
||||
|
||||
- Jan Borsodi (`@am0s`_)
|
||||
- Ryan Kraus (`@rmkraus`_)
|
||||
- Paulus Schoutsen (`@balloob`_, original author)
|
||||
|
||||
Thanks
|
||||
------
|
||||
|
||||
I would like to thank `Fred Clift`_ for laying the socket client ground
|
||||
work. Without him it would not have been possible!
|
||||
|
||||
.. _Linux style wildcard: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
|
||||
.. _@am0s: https://github.com/am0s
|
||||
.. _@rmkraus: https://github.com/rmkraus
|
||||
.. _@balloob: https://github.com/balloob
|
||||
.. _Fred Clift: https://github.com/minektur
|
||||
|
||||
|
42
deps/PyChromecast-0.7.4.dist-info/RECORD
vendored
Normal file
42
deps/PyChromecast-0.7.4.dist-info/RECORD
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
.DS_Store,sha256=z2gRZJvEM_WGkHGXKJ65r7UPG7qICbiawVsPXEnp-8c,6148
|
||||
PyChromecast-0.7.4.dist-info/DESCRIPTION.rst,sha256=SSmbtGe3rWS2588qTAdJ0VQvN9YzIT8RPIyhOgyxA6A,7126
|
||||
PyChromecast-0.7.4.dist-info/METADATA,sha256=S_HTSuuPz09zMaXxGrFtgHo5C_-eWaS1GDEtJviJhPM,7866
|
||||
PyChromecast-0.7.4.dist-info/RECORD,,
|
||||
PyChromecast-0.7.4.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110
|
||||
PyChromecast-0.7.4.dist-info/metadata.json,sha256=kJPpgubHP3GQOpcv9gIQcba_QDjnDxnjPh9tOJQHr0o,903
|
||||
PyChromecast-0.7.4.dist-info/pbr.json,sha256=WvxHilx5XoxsLZlvp6nyc79cgMObi5TSKj6KUiSdTe4,47
|
||||
PyChromecast-0.7.4.dist-info/top_level.txt,sha256=rO8Gp9im127G1t-Kzk0r2Lh2y19ZaIG91OBRZpw0gTA,13
|
||||
pychromecast/.DS_Store,sha256=oDO_w-SWfCkAPjvaG6BL1qB2KvyzpPpFv8MbYH9fljg,6148
|
||||
pychromecast/__init__.py,sha256=U3Y46fioyGyvkNNDaulVorIspSbFK6rpVPKeANWIPPY,15479
|
||||
pychromecast/authority_keys_pb2.py,sha256=b4lL5q50P1OJNn-z2JvkUF3uoc347geHiFqeyV8kq-w,3985
|
||||
pychromecast/cast_channel_pb2.py,sha256=JPXrpcOWoeNIOWf9-FZFooAfYLgrxSDKfiIuReCmCpk,16191
|
||||
pychromecast/config.py,sha256=28hjJlQ2vzZbqS12VW6a42ry6NNo1khMJ9NDYyd2zm8,1012
|
||||
pychromecast/dial.py,sha256=_-rSh3r8_MIWwfKY1Wqs7NEAKKcE0K0Vnhl1VVrO0KU,3877
|
||||
pychromecast/discovery.py,sha256=1S5wvFaf-4WrRKfdfe7-stPEP8TLU2L6GeIAmnn36oU,2578
|
||||
pychromecast/error.py,sha256=4tQQ-I4c2k0OgJWsn-kAcR1konUPq2DzfB6qtSJ2oUw,1334
|
||||
pychromecast/logging_pb2.py,sha256=2vOWKAt7iBjp16sdOwpFvDSlHtzQqq13qoKR7bL6Dik,34008
|
||||
pychromecast/socket_client.py,sha256=9uET2UNvjW4ra5RgdniWlJBhat54lqqh_wmpGhfWOHw,33787
|
||||
pychromecast/upnp.py,sha256=A8WsgB5ay98e35T4b-UxxItcQVQOka4TICbi8JUv-mg,2732
|
||||
pychromecast/websocket.py,sha256=9ydyIh7b7eoIA_yXvk-HRuHowhPd9bmQaoyLYgXn4y4,13417
|
||||
pychromecast/youtube.py,sha256=2iHHo-VnvSjWY2xKjd8lGDqvg3sYpqaExWnx-Puccxc,1054
|
||||
pychromecast/controllers/__init__.py,sha256=qUwhRxqUi8bRO-ejv2azlGbm776wh4iiWEIp4lJ-IkQ,3593
|
||||
pychromecast/controllers/media.py,sha256=4HNKUTsejuRU2w8AtGLrifuN-mtYRW-HP-t5QIo28cU,16129
|
||||
pychromecast/controllers/plex.py,sha256=u1C5WGkoqAlzGdvNF1nCo9riuDW9s_idxhfhY4ZupXQ,703
|
||||
pychromecast/controllers/youtube.py,sha256=eTZb0qK4fua9ai3UQFDcJMcIMfLTAVPwWSlGIvUXI6Y,1293
|
||||
PyChromecast-0.7.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pychromecast/__pycache__/logging_pb2.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/authority_keys_pb2.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/dial.cpython-34.pyc,,
|
||||
pychromecast/controllers/__pycache__/__init__.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/upnp.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/socket_client.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/error.cpython-34.pyc,,
|
||||
pychromecast/controllers/__pycache__/youtube.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/config.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/cast_channel_pb2.cpython-34.pyc,,
|
||||
pychromecast/controllers/__pycache__/plex.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/__init__.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/youtube.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/discovery.cpython-34.pyc,,
|
||||
pychromecast/controllers/__pycache__/media.cpython-34.pyc,,
|
||||
pychromecast/__pycache__/websocket.cpython-34.pyc,,
|
6
deps/PyChromecast-0.7.4.dist-info/WHEEL
vendored
Normal file
6
deps/PyChromecast-0.7.4.dist-info/WHEEL
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.26.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
1
deps/PyChromecast-0.7.4.dist-info/metadata.json
vendored
Normal file
1
deps/PyChromecast-0.7.4.dist-info/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"generator": "bdist_wheel (0.26.0)", "summary": "Python module to talk to Google Chromecast.", "classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules"], "extensions": {"python.details": {"project_urls": {"Home": "https://github.com/balloob/pychromecast"}, "contacts": [{"email": "paulus@paulusschoutsen.nl", "name": "Paulus Schoutsen", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}}}, "license": "MIT", "metadata_version": "2.0", "name": "PyChromecast", "platform": "any", "run_requires": [{"requires": ["protobuf (==3.0.0b2)", "requests (>=2.0)", "six (>=1.10.0)", "zeroconf (>=0.17.4)"]}], "extras": [], "version": "0.7.4"}
|
1
deps/PyChromecast-0.7.4.dist-info/pbr.json
vendored
Normal file
1
deps/PyChromecast-0.7.4.dist-info/pbr.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"git_version": "03f56b3", "is_release": false}
|
1
deps/PyChromecast-0.7.4.dist-info/top_level.txt
vendored
Normal file
1
deps/PyChromecast-0.7.4.dist-info/top_level.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pychromecast
|
204
deps/RPi.GPIO-0.6.2.dist-info/DESCRIPTION.rst
vendored
Normal file
204
deps/RPi.GPIO-0.6.2.dist-info/DESCRIPTION.rst
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
This package provides a class to control the GPIO on a Raspberry Pi.
|
||||
|
||||
Note that this module is unsuitable for real-time or timing critical applications. This is because you
|
||||
can not predict when Python will be busy garbage collecting. It also runs under the Linux kernel which
|
||||
is not suitable for real time applications - it is multitasking O/S and another process may be given
|
||||
priority over the CPU, causing jitter in your program. If you are after true real-time performance and
|
||||
predictability, buy yourself an Arduino http://www.arduino.cc !
|
||||
|
||||
Note that the current release does not support SPI, I2C, hardware PWM or serial functionality on the RPi yet.
|
||||
This is planned for the near future - watch this space! One-wire functionality is also planned.
|
||||
|
||||
Although hardware PWM is not available yet, software PWM is available to use on all channels.
|
||||
|
||||
For examples and documentation, visit http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
|
||||
|
||||
Change Log
|
||||
==========
|
||||
|
||||
0.6.2
|
||||
-----
|
||||
- Rewrote Debian packaging mechanism
|
||||
- RPI_INFO reports Pi 3
|
||||
- Changed module layout - moved C components to RPi._GPIO
|
||||
|
||||
0.6.1
|
||||
-----
|
||||
- Update RPI_INFO to detect more board types
|
||||
- Issue 118 - add_event_detect sometimes gives runtime error with unpriv user
|
||||
- Issue 120 - setmode() remembers invalid mode
|
||||
|
||||
0.6.0a3
|
||||
-------
|
||||
- Now uses /dev/gpiomem if available to avoid being run as root
|
||||
- Fix warnings with pull up/down on pins 3/5
|
||||
- Correct base address on Pi 2 when devicetree is disabled
|
||||
- caddr_t error on compile (Issue 109)
|
||||
- Error on invalid parameters to setup() (issue 93)
|
||||
- Add timeout parameter to wait_for_edge() (issue 91)
|
||||
|
||||
0.5.11
|
||||
------
|
||||
- Fix - pins > 26 missing when using BOARD mode
|
||||
- Add getmode()
|
||||
- Raise exception when a mix of modes is used
|
||||
- GPIO.cleanaup() unsets the current pin mode
|
||||
|
||||
0.5.10
|
||||
------
|
||||
- Issue 95 - support RPi 2 boards
|
||||
- Introduce RPI_INFO
|
||||
- Deprecate RPI_REVISION
|
||||
- Issue 97 - fixed docstring for setup()
|
||||
|
||||
0.5.9
|
||||
-----
|
||||
- Issue 87 - warn about pull up/down on i2c pins
|
||||
- Issue 86/75 - wait_for_edge() bugfix
|
||||
- Issue 84 - recognise RPi properly when using a custom kernel
|
||||
- Issue 90 - cleanup() on a list/tuple of channels
|
||||
|
||||
0.5.8
|
||||
-----
|
||||
- Allow lists/tuples of channels in GPIO.setup()
|
||||
- GPIO.output() now allows lists/tuples of values
|
||||
- GPIO.wait_for_edge() bug fixes (issue 78)
|
||||
|
||||
0.5.7
|
||||
-----
|
||||
- Issue 67 - speed up repeated calls to GPIO.wait_for_event()
|
||||
- Added bouncetime keyword to GPIO.wait_for_event()
|
||||
- Added extra edge/interrupt unit tests
|
||||
- GPIO.wait_for_event() can now be mixed with GPIO.add_event_detect()
|
||||
- Improved cleanups of events
|
||||
- Issue 69 resolved
|
||||
|
||||
0.5.6
|
||||
-----
|
||||
- Issue 68 - support for RPi Model B+
|
||||
- Fix gpio_function()
|
||||
|
||||
0.5.5
|
||||
-----
|
||||
- Issue 52 - 'unallocate' a channel
|
||||
- Issue 35 - use switchbounce with GPIO.event_detected()
|
||||
- Refactored events code
|
||||
- Rewrote tests to use unittest mechanism and new test board with loopbacks
|
||||
- Fixed adding events after a GPIO.cleanup()
|
||||
- Issue 64 - misleading /dev/mem permissions error
|
||||
- Issue 59 - name collision with PWM constant and class
|
||||
|
||||
0.5.4
|
||||
-----
|
||||
- Changed release status (from alpha to full release)
|
||||
- Warn when GPIO.cleanup() used with nothing to clean up (issue 44)
|
||||
- Avoid collisions in constants (e.g. HIGH / RISING / PUD_DOWN)
|
||||
- Accept BOARD numbers in gpio_function (issue 34)
|
||||
- More return values for gpio_function (INPUT, OUTPUT, SPI, I2C, PWM, SERIAL, UNKNOWN)
|
||||
- Tidy up docstrings
|
||||
- Fix /dev/mem access error with gpio_function
|
||||
|
||||
0.5.3a
|
||||
------
|
||||
- Allow pydoc for non-root users (issue 27)
|
||||
- Fix add_event_detect error when run as daemon (issue 32)
|
||||
- Simplified exception types
|
||||
- Changed from distribute to pip
|
||||
|
||||
0.5.2a
|
||||
------
|
||||
- Added software PWM (experimental)
|
||||
- Added switch bounce handling to event callbacks
|
||||
- Added channel number parameter to event callbacks (issue 31)
|
||||
- Internal refactoring and code tidy up
|
||||
|
||||
0.5.1a
|
||||
------
|
||||
- Fixed callbacks for multiple GPIOs (issue 28)
|
||||
|
||||
0.5.0a
|
||||
------
|
||||
- Added new edge detection events (interrupt handling)
|
||||
- Added add_event_detect()
|
||||
- Added remove_event_detect()
|
||||
- Added add_event_callback()
|
||||
- Added wait_for_edge()
|
||||
- Removed old experimental event functions
|
||||
- Removed set_rising_event()
|
||||
- Removed set_falling_event()
|
||||
- Removed set_high_event()
|
||||
- Removed set_low_event()
|
||||
- Changed event_detected() for new edge detection functionality
|
||||
- input() now returns 0/LOW == False or 1/HIGH == True (integers) instead of False or True (booleans).
|
||||
- Fix error on repeated import (issue 3)
|
||||
- Change SetupException to a RuntimeError so it can be caught on import (issue 25, Chris Hager <chris@linuxuser.at>)
|
||||
- Improved docstrings of functions
|
||||
|
||||
0.4.2a
|
||||
------
|
||||
- Fix for installing on Arch Linux (Python 3.3) (issue 20)
|
||||
- Initial value when setting a channel as an output (issue 19)
|
||||
|
||||
0.4.1a
|
||||
------
|
||||
- Added VERSION
|
||||
- Permit input() of channels set as outputs (Eric Ptak <trouch@trouch.com>)
|
||||
|
||||
0.4.0a
|
||||
------
|
||||
- Added support for Revision 2 boards
|
||||
- Added RPI_REVISION
|
||||
- Added cleanup() function and removed automatic reset functionality on program exit
|
||||
- Added get_function() to read existing GPIO channel functionality (suggestion from Eric Ptak <trouch@trouch.com>)
|
||||
- Added set_rising_event()
|
||||
- Added set_falling_event()
|
||||
- Added set_high_event()
|
||||
- Added set_low_event()
|
||||
- Added event_detected()
|
||||
- Added test/test.py
|
||||
- Converted debian to armhf
|
||||
- Fixed C function short_wait() (thanks to Thibault Porteboeuf <thibaultporteboeuf@gmail.com>)
|
||||
|
||||
0.3.1a
|
||||
------
|
||||
- Fixed critical bug with swapped high/low state on outputs
|
||||
- Added pull-up / pull-down setup functionality for inputs
|
||||
|
||||
0.3.0a
|
||||
------
|
||||
- Rewritten as a C extension
|
||||
- Now uses /dev/mem and SoC registers instead of /sys/class/gpio
|
||||
- Faster!
|
||||
- Make call to GPIO.setmode() mandatory
|
||||
- Added GPIO.HIGH and GPIO.LOW constants
|
||||
|
||||
0.2.0
|
||||
-----
|
||||
- Changed status from alpha to beta
|
||||
- Added setmode() to be able to use BCM GPIO 00.nn channel numbers
|
||||
- Renamed InvalidPinException to InvalidChannelException
|
||||
|
||||
0.1.0
|
||||
------
|
||||
- Fixed direction bug
|
||||
- Added MANIFEST.in (to include missing file)
|
||||
- Changed GPIO channel number to pin number
|
||||
- Tested and working!
|
||||
|
||||
0.0.3a
|
||||
------
|
||||
- Added GPIO table
|
||||
- Refactored
|
||||
- Fixed a few critical bugs
|
||||
- Still completely untested!
|
||||
|
||||
0.0.2a
|
||||
------
|
||||
- Internal refactoring. Still completely untested!
|
||||
|
||||
0.0.1a
|
||||
------
|
||||
- First version. Completely untested until I can get hold of a Raspberry Pi!
|
||||
|
||||
|
||||
|
1
deps/RPi.GPIO-0.6.2.dist-info/INSTALLER
vendored
Normal file
1
deps/RPi.GPIO-0.6.2.dist-info/INSTALLER
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
224
deps/RPi.GPIO-0.6.2.dist-info/METADATA
vendored
Normal file
224
deps/RPi.GPIO-0.6.2.dist-info/METADATA
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: RPi.GPIO
|
||||
Version: 0.6.2
|
||||
Summary: A module to control Raspberry Pi GPIO channels
|
||||
Home-page: http://sourceforge.net/projects/raspberry-gpio-python/
|
||||
Author: Ben Croston
|
||||
Author-email: ben@croston.org
|
||||
License: MIT
|
||||
Keywords: Raspberry Pi GPIO
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Topic :: Home Automation
|
||||
Classifier: Topic :: System :: Hardware
|
||||
|
||||
This package provides a class to control the GPIO on a Raspberry Pi.
|
||||
|
||||
Note that this module is unsuitable for real-time or timing critical applications. This is because you
|
||||
can not predict when Python will be busy garbage collecting. It also runs under the Linux kernel which
|
||||
is not suitable for real time applications - it is multitasking O/S and another process may be given
|
||||
priority over the CPU, causing jitter in your program. If you are after true real-time performance and
|
||||
predictability, buy yourself an Arduino http://www.arduino.cc !
|
||||
|
||||
Note that the current release does not support SPI, I2C, hardware PWM or serial functionality on the RPi yet.
|
||||
This is planned for the near future - watch this space! One-wire functionality is also planned.
|
||||
|
||||
Although hardware PWM is not available yet, software PWM is available to use on all channels.
|
||||
|
||||
For examples and documentation, visit http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
|
||||
|
||||
Change Log
|
||||
==========
|
||||
|
||||
0.6.2
|
||||
-----
|
||||
- Rewrote Debian packaging mechanism
|
||||
- RPI_INFO reports Pi 3
|
||||
- Changed module layout - moved C components to RPi._GPIO
|
||||
|
||||
0.6.1
|
||||
-----
|
||||
- Update RPI_INFO to detect more board types
|
||||
- Issue 118 - add_event_detect sometimes gives runtime error with unpriv user
|
||||
- Issue 120 - setmode() remembers invalid mode
|
||||
|
||||
0.6.0a3
|
||||
-------
|
||||
- Now uses /dev/gpiomem if available to avoid being run as root
|
||||
- Fix warnings with pull up/down on pins 3/5
|
||||
- Correct base address on Pi 2 when devicetree is disabled
|
||||
- caddr_t error on compile (Issue 109)
|
||||
- Error on invalid parameters to setup() (issue 93)
|
||||
- Add timeout parameter to wait_for_edge() (issue 91)
|
||||
|
||||
0.5.11
|
||||
------
|
||||
- Fix - pins > 26 missing when using BOARD mode
|
||||
- Add getmode()
|
||||
- Raise exception when a mix of modes is used
|
||||
- GPIO.cleanaup() unsets the current pin mode
|
||||
|
||||
0.5.10
|
||||
------
|
||||
- Issue 95 - support RPi 2 boards
|
||||
- Introduce RPI_INFO
|
||||
- Deprecate RPI_REVISION
|
||||
- Issue 97 - fixed docstring for setup()
|
||||
|
||||
0.5.9
|
||||
-----
|
||||
- Issue 87 - warn about pull up/down on i2c pins
|
||||
- Issue 86/75 - wait_for_edge() bugfix
|
||||
- Issue 84 - recognise RPi properly when using a custom kernel
|
||||
- Issue 90 - cleanup() on a list/tuple of channels
|
||||
|
||||
0.5.8
|
||||
-----
|
||||
- Allow lists/tuples of channels in GPIO.setup()
|
||||
- GPIO.output() now allows lists/tuples of values
|
||||
- GPIO.wait_for_edge() bug fixes (issue 78)
|
||||
|
||||
0.5.7
|
||||
-----
|
||||
- Issue 67 - speed up repeated calls to GPIO.wait_for_event()
|
||||
- Added bouncetime keyword to GPIO.wait_for_event()
|
||||
- Added extra edge/interrupt unit tests
|
||||
- GPIO.wait_for_event() can now be mixed with GPIO.add_event_detect()
|
||||
- Improved cleanups of events
|
||||
- Issue 69 resolved
|
||||
|
||||
0.5.6
|
||||
-----
|
||||
- Issue 68 - support for RPi Model B+
|
||||
- Fix gpio_function()
|
||||
|
||||
0.5.5
|
||||
-----
|
||||
- Issue 52 - 'unallocate' a channel
|
||||
- Issue 35 - use switchbounce with GPIO.event_detected()
|
||||
- Refactored events code
|
||||
- Rewrote tests to use unittest mechanism and new test board with loopbacks
|
||||
- Fixed adding events after a GPIO.cleanup()
|
||||
- Issue 64 - misleading /dev/mem permissions error
|
||||
- Issue 59 - name collision with PWM constant and class
|
||||
|
||||
0.5.4
|
||||
-----
|
||||
- Changed release status (from alpha to full release)
|
||||
- Warn when GPIO.cleanup() used with nothing to clean up (issue 44)
|
||||
- Avoid collisions in constants (e.g. HIGH / RISING / PUD_DOWN)
|
||||
- Accept BOARD numbers in gpio_function (issue 34)
|
||||
- More return values for gpio_function (INPUT, OUTPUT, SPI, I2C, PWM, SERIAL, UNKNOWN)
|
||||
- Tidy up docstrings
|
||||
- Fix /dev/mem access error with gpio_function
|
||||
|
||||
0.5.3a
|
||||
------
|
||||
- Allow pydoc for non-root users (issue 27)
|
||||
- Fix add_event_detect error when run as daemon (issue 32)
|
||||
- Simplified exception types
|
||||
- Changed from distribute to pip
|
||||
|
||||
0.5.2a
|
||||
------
|
||||
- Added software PWM (experimental)
|
||||
- Added switch bounce handling to event callbacks
|
||||
- Added channel number parameter to event callbacks (issue 31)
|
||||
- Internal refactoring and code tidy up
|
||||
|
||||
0.5.1a
|
||||
------
|
||||
- Fixed callbacks for multiple GPIOs (issue 28)
|
||||
|
||||
0.5.0a
|
||||
------
|
||||
- Added new edge detection events (interrupt handling)
|
||||
- Added add_event_detect()
|
||||
- Added remove_event_detect()
|
||||
- Added add_event_callback()
|
||||
- Added wait_for_edge()
|
||||
- Removed old experimental event functions
|
||||
- Removed set_rising_event()
|
||||
- Removed set_falling_event()
|
||||
- Removed set_high_event()
|
||||
- Removed set_low_event()
|
||||
- Changed event_detected() for new edge detection functionality
|
||||
- input() now returns 0/LOW == False or 1/HIGH == True (integers) instead of False or True (booleans).
|
||||
- Fix error on repeated import (issue 3)
|
||||
- Change SetupException to a RuntimeError so it can be caught on import (issue 25, Chris Hager <chris@linuxuser.at>)
|
||||
- Improved docstrings of functions
|
||||
|
||||
0.4.2a
|
||||
------
|
||||
- Fix for installing on Arch Linux (Python 3.3) (issue 20)
|
||||
- Initial value when setting a channel as an output (issue 19)
|
||||
|
||||
0.4.1a
|
||||
------
|
||||
- Added VERSION
|
||||
- Permit input() of channels set as outputs (Eric Ptak <trouch@trouch.com>)
|
||||
|
||||
0.4.0a
|
||||
------
|
||||
- Added support for Revision 2 boards
|
||||
- Added RPI_REVISION
|
||||
- Added cleanup() function and removed automatic reset functionality on program exit
|
||||
- Added get_function() to read existing GPIO channel functionality (suggestion from Eric Ptak <trouch@trouch.com>)
|
||||
- Added set_rising_event()
|
||||
- Added set_falling_event()
|
||||
- Added set_high_event()
|
||||
- Added set_low_event()
|
||||
- Added event_detected()
|
||||
- Added test/test.py
|
||||
- Converted debian to armhf
|
||||
- Fixed C function short_wait() (thanks to Thibault Porteboeuf <thibaultporteboeuf@gmail.com>)
|
||||
|
||||
0.3.1a
|
||||
------
|
||||
- Fixed critical bug with swapped high/low state on outputs
|
||||
- Added pull-up / pull-down setup functionality for inputs
|
||||
|
||||
0.3.0a
|
||||
------
|
||||
- Rewritten as a C extension
|
||||
- Now uses /dev/mem and SoC registers instead of /sys/class/gpio
|
||||
- Faster!
|
||||
- Make call to GPIO.setmode() mandatory
|
||||
- Added GPIO.HIGH and GPIO.LOW constants
|
||||
|
||||
0.2.0
|
||||
-----
|
||||
- Changed status from alpha to beta
|
||||
- Added setmode() to be able to use BCM GPIO 00.nn channel numbers
|
||||
- Renamed InvalidPinException to InvalidChannelException
|
||||
|
||||
0.1.0
|
||||
------
|
||||
- Fixed direction bug
|
||||
- Added MANIFEST.in (to include missing file)
|
||||
- Changed GPIO channel number to pin number
|
||||
- Tested and working!
|
||||
|
||||
0.0.3a
|
||||
------
|
||||
- Added GPIO table
|
||||
- Refactored
|
||||
- Fixed a few critical bugs
|
||||
- Still completely untested!
|
||||
|
||||
0.0.2a
|
||||
------
|
||||
- Internal refactoring. Still completely untested!
|
||||
|
||||
0.0.1a
|
||||
------
|
||||
- First version. Completely untested until I can get hold of a Raspberry Pi!
|
||||
|
||||
|
||||
|
12
deps/RPi.GPIO-0.6.2.dist-info/RECORD
vendored
Normal file
12
deps/RPi.GPIO-0.6.2.dist-info/RECORD
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
RPi/_GPIO.cpython-34m.so,sha256=vAgqvC2ElrF2cSyFgxJ_SvDLSJ6m5h6R77GxoBQ9nDg,132144
|
||||
RPi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
RPi/GPIO/__init__.py,sha256=jnxwfrIfmsuc4CHYCyD7fhmXuK910T8nJZly3TFbZ1s,1112
|
||||
RPi.GPIO-0.6.2.dist-info/DESCRIPTION.rst,sha256=XQZ6vEQCpIDCQyetosBFH2erMMSGGq-K6mG1Lw99Yrw,6289
|
||||
RPi.GPIO-0.6.2.dist-info/METADATA,sha256=_m7PpAOt0icvoUp0OCBMJRRu-HCWgyoWnSg-4Bnn9Jg,6988
|
||||
RPi.GPIO-0.6.2.dist-info/RECORD,,
|
||||
RPi.GPIO-0.6.2.dist-info/WHEEL,sha256=qlXKyUxaKdKGVZKHdQaMFOPNWg5qC8OVyb-aicJwJ6U,104
|
||||
RPi.GPIO-0.6.2.dist-info/metadata.json,sha256=tQq4Ev0lJJDCwtSI_mlRDqSWLcCT9vEVAO9VKv7fg8Y,834
|
||||
RPi.GPIO-0.6.2.dist-info/top_level.txt,sha256=D2ebmx5QNuKCb-J2LbVMXkkhIVNpXvmxlOVIZjPsALw,4
|
||||
RPi.GPIO-0.6.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
RPi/GPIO/__pycache__/__init__.cpython-34.pyc,,
|
||||
RPi/__pycache__/__init__.cpython-34.pyc,,
|
5
deps/RPi.GPIO-0.6.2.dist-info/WHEEL
vendored
Normal file
5
deps/RPi.GPIO-0.6.2.dist-info/WHEEL
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp34-cp34m-linux_armv7l
|
||||
|
1
deps/RPi.GPIO-0.6.2.dist-info/metadata.json
vendored
Normal file
1
deps/RPi.GPIO-0.6.2.dist-info/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"classifiers": ["Development Status :: 5 - Production/Stable", "Operating System :: POSIX :: Linux", "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Topic :: Software Development", "Topic :: Home Automation", "Topic :: System :: Hardware"], "extensions": {"python.details": {"contacts": [{"email": "ben@croston.org", "name": "Ben Croston", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://sourceforge.net/projects/raspberry-gpio-python/"}}}, "generator": "bdist_wheel (0.29.0)", "keywords": ["Raspberry", "Pi", "GPIO"], "license": "MIT", "metadata_version": "2.0", "name": "RPi.GPIO", "summary": "A module to control Raspberry Pi GPIO channels", "version": "0.6.2"}
|
1
deps/RPi.GPIO-0.6.2.dist-info/top_level.txt
vendored
Normal file
1
deps/RPi.GPIO-0.6.2.dist-info/top_level.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
RPi
|
25
deps/RPi/GPIO/__init__.py
vendored
Normal file
25
deps/RPi/GPIO/__init__.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Copyright (c) 2012-2016 Ben Croston
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from RPi._GPIO import *
|
||||
|
||||
VERSION = '0.6.2'
|
BIN
deps/RPi/GPIO/__pycache__/__init__.cpython-34.pyc
vendored
Normal file
BIN
deps/RPi/GPIO/__pycache__/__init__.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/RPi/_GPIO.cpython-34m.so
vendored
Executable file
BIN
deps/RPi/_GPIO.cpython-34m.so
vendored
Executable file
Binary file not shown.
0
deps/RPi/__init__.py
vendored
Normal file
0
deps/RPi/__init__.py
vendored
Normal file
BIN
deps/RPi/__pycache__/__init__.cpython-34.pyc
vendored
Normal file
BIN
deps/RPi/__pycache__/__init__.cpython-34.pyc
vendored
Normal file
Binary file not shown.
137
deps/SQLAlchemy-1.0.15.dist-info/DESCRIPTION.rst
vendored
Normal file
137
deps/SQLAlchemy-1.0.15.dist-info/DESCRIPTION.rst
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
SQLAlchemy
|
||||
==========
|
||||
|
||||
The Python SQL Toolkit and Object Relational Mapper
|
||||
|
||||
Introduction
|
||||
-------------
|
||||
|
||||
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper
|
||||
that gives application developers the full power and
|
||||
flexibility of SQL. SQLAlchemy provides a full suite
|
||||
of well known enterprise-level persistence patterns,
|
||||
designed for efficient and high-performing database
|
||||
access, adapted into a simple and Pythonic domain
|
||||
language.
|
||||
|
||||
Major SQLAlchemy features include:
|
||||
|
||||
* An industrial strength ORM, built
|
||||
from the core on the identity map, unit of work,
|
||||
and data mapper patterns. These patterns
|
||||
allow transparent persistence of objects
|
||||
using a declarative configuration system.
|
||||
Domain models
|
||||
can be constructed and manipulated naturally,
|
||||
and changes are synchronized with the
|
||||
current transaction automatically.
|
||||
* A relationally-oriented query system, exposing
|
||||
the full range of SQL's capabilities
|
||||
explicitly, including joins, subqueries,
|
||||
correlation, and most everything else,
|
||||
in terms of the object model.
|
||||
Writing queries with the ORM uses the same
|
||||
techniques of relational composition you use
|
||||
when writing SQL. While you can drop into
|
||||
literal SQL at any time, it's virtually never
|
||||
needed.
|
||||
* A comprehensive and flexible system
|
||||
of eager loading for related collections and objects.
|
||||
Collections are cached within a session,
|
||||
and can be loaded on individual access, all
|
||||
at once using joins, or by query per collection
|
||||
across the full result set.
|
||||
* A Core SQL construction system and DBAPI
|
||||
interaction layer. The SQLAlchemy Core is
|
||||
separate from the ORM and is a full database
|
||||
abstraction layer in its own right, and includes
|
||||
an extensible Python-based SQL expression
|
||||
language, schema metadata, connection pooling,
|
||||
type coercion, and custom types.
|
||||
* All primary and foreign key constraints are
|
||||
assumed to be composite and natural. Surrogate
|
||||
integer primary keys are of course still the
|
||||
norm, but SQLAlchemy never assumes or hardcodes
|
||||
to this model.
|
||||
* Database introspection and generation. Database
|
||||
schemas can be "reflected" in one step into
|
||||
Python structures representing database metadata;
|
||||
those same structures can then generate
|
||||
CREATE statements right back out - all within
|
||||
the Core, independent of the ORM.
|
||||
|
||||
SQLAlchemy's philosophy:
|
||||
|
||||
* SQL databases behave less and less like object
|
||||
collections the more size and performance start to
|
||||
matter; object collections behave less and less like
|
||||
tables and rows the more abstraction starts to matter.
|
||||
SQLAlchemy aims to accommodate both of these
|
||||
principles.
|
||||
* An ORM doesn't need to hide the "R". A relational
|
||||
database provides rich, set-based functionality
|
||||
that should be fully exposed. SQLAlchemy's
|
||||
ORM provides an open-ended set of patterns
|
||||
that allow a developer to construct a custom
|
||||
mediation layer between a domain model and
|
||||
a relational schema, turning the so-called
|
||||
"object relational impedance" issue into
|
||||
a distant memory.
|
||||
* The developer, in all cases, makes all decisions
|
||||
regarding the design, structure, and naming conventions
|
||||
of both the object model as well as the relational
|
||||
schema. SQLAlchemy only provides the means
|
||||
to automate the execution of these decisions.
|
||||
* With SQLAlchemy, there's no such thing as
|
||||
"the ORM generated a bad query" - you
|
||||
retain full control over the structure of
|
||||
queries, including how joins are organized,
|
||||
how subqueries and correlation is used, what
|
||||
columns are requested. Everything SQLAlchemy
|
||||
does is ultimately the result of a developer-
|
||||
initiated decision.
|
||||
* Don't use an ORM if the problem doesn't need one.
|
||||
SQLAlchemy consists of a Core and separate ORM
|
||||
component. The Core offers a full SQL expression
|
||||
language that allows Pythonic construction
|
||||
of SQL constructs that render directly to SQL
|
||||
strings for a target database, returning
|
||||
result sets that are essentially enhanced DBAPI
|
||||
cursors.
|
||||
* Transactions should be the norm. With SQLAlchemy's
|
||||
ORM, nothing goes to permanent storage until
|
||||
commit() is called. SQLAlchemy encourages applications
|
||||
to create a consistent means of delineating
|
||||
the start and end of a series of operations.
|
||||
* Never render a literal value in a SQL statement.
|
||||
Bound parameters are used to the greatest degree
|
||||
possible, allowing query optimizers to cache
|
||||
query plans effectively and making SQL injection
|
||||
attacks a non-issue.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Latest documentation is at:
|
||||
|
||||
http://www.sqlalchemy.org/docs/
|
||||
|
||||
Installation / Requirements
|
||||
---------------------------
|
||||
|
||||
Full documentation for installation is at
|
||||
`Installation <http://www.sqlalchemy.org/docs/intro.html#installation>`_.
|
||||
|
||||
Getting Help / Development / Bug reporting
|
||||
------------------------------------------
|
||||
|
||||
Please refer to the `SQLAlchemy Community Guide <http://www.sqlalchemy.org/support.html>`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
SQLAlchemy is distributed under the `MIT license
|
||||
<http://www.opensource.org/licenses/mit-license.php>`_.
|
||||
|
||||
|
||||
|
1
deps/SQLAlchemy-1.0.15.dist-info/INSTALLER
vendored
Normal file
1
deps/SQLAlchemy-1.0.15.dist-info/INSTALLER
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
157
deps/SQLAlchemy-1.0.15.dist-info/METADATA
vendored
Normal file
157
deps/SQLAlchemy-1.0.15.dist-info/METADATA
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: SQLAlchemy
|
||||
Version: 1.0.15
|
||||
Summary: Database Abstraction Library
|
||||
Home-page: http://www.sqlalchemy.org
|
||||
Author: Mike Bayer
|
||||
Author-email: mike_mp@zzzcomputing.com
|
||||
License: MIT License
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: Jython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Database :: Front-Ends
|
||||
Classifier: Operating System :: OS Independent
|
||||
|
||||
SQLAlchemy
|
||||
==========
|
||||
|
||||
The Python SQL Toolkit and Object Relational Mapper
|
||||
|
||||
Introduction
|
||||
-------------
|
||||
|
||||
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper
|
||||
that gives application developers the full power and
|
||||
flexibility of SQL. SQLAlchemy provides a full suite
|
||||
of well known enterprise-level persistence patterns,
|
||||
designed for efficient and high-performing database
|
||||
access, adapted into a simple and Pythonic domain
|
||||
language.
|
||||
|
||||
Major SQLAlchemy features include:
|
||||
|
||||
* An industrial strength ORM, built
|
||||
from the core on the identity map, unit of work,
|
||||
and data mapper patterns. These patterns
|
||||
allow transparent persistence of objects
|
||||
using a declarative configuration system.
|
||||
Domain models
|
||||
can be constructed and manipulated naturally,
|
||||
and changes are synchronized with the
|
||||
current transaction automatically.
|
||||
* A relationally-oriented query system, exposing
|
||||
the full range of SQL's capabilities
|
||||
explicitly, including joins, subqueries,
|
||||
correlation, and most everything else,
|
||||
in terms of the object model.
|
||||
Writing queries with the ORM uses the same
|
||||
techniques of relational composition you use
|
||||
when writing SQL. While you can drop into
|
||||
literal SQL at any time, it's virtually never
|
||||
needed.
|
||||
* A comprehensive and flexible system
|
||||
of eager loading for related collections and objects.
|
||||
Collections are cached within a session,
|
||||
and can be loaded on individual access, all
|
||||
at once using joins, or by query per collection
|
||||
across the full result set.
|
||||
* A Core SQL construction system and DBAPI
|
||||
interaction layer. The SQLAlchemy Core is
|
||||
separate from the ORM and is a full database
|
||||
abstraction layer in its own right, and includes
|
||||
an extensible Python-based SQL expression
|
||||
language, schema metadata, connection pooling,
|
||||
type coercion, and custom types.
|
||||
* All primary and foreign key constraints are
|
||||
assumed to be composite and natural. Surrogate
|
||||
integer primary keys are of course still the
|
||||
norm, but SQLAlchemy never assumes or hardcodes
|
||||
to this model.
|
||||
* Database introspection and generation. Database
|
||||
schemas can be "reflected" in one step into
|
||||
Python structures representing database metadata;
|
||||
those same structures can then generate
|
||||
CREATE statements right back out - all within
|
||||
the Core, independent of the ORM.
|
||||
|
||||
SQLAlchemy's philosophy:
|
||||
|
||||
* SQL databases behave less and less like object
|
||||
collections the more size and performance start to
|
||||
matter; object collections behave less and less like
|
||||
tables and rows the more abstraction starts to matter.
|
||||
SQLAlchemy aims to accommodate both of these
|
||||
principles.
|
||||
* An ORM doesn't need to hide the "R". A relational
|
||||
database provides rich, set-based functionality
|
||||
that should be fully exposed. SQLAlchemy's
|
||||
ORM provides an open-ended set of patterns
|
||||
that allow a developer to construct a custom
|
||||
mediation layer between a domain model and
|
||||
a relational schema, turning the so-called
|
||||
"object relational impedance" issue into
|
||||
a distant memory.
|
||||
* The developer, in all cases, makes all decisions
|
||||
regarding the design, structure, and naming conventions
|
||||
of both the object model as well as the relational
|
||||
schema. SQLAlchemy only provides the means
|
||||
to automate the execution of these decisions.
|
||||
* With SQLAlchemy, there's no such thing as
|
||||
"the ORM generated a bad query" - you
|
||||
retain full control over the structure of
|
||||
queries, including how joins are organized,
|
||||
how subqueries and correlation is used, what
|
||||
columns are requested. Everything SQLAlchemy
|
||||
does is ultimately the result of a developer-
|
||||
initiated decision.
|
||||
* Don't use an ORM if the problem doesn't need one.
|
||||
SQLAlchemy consists of a Core and separate ORM
|
||||
component. The Core offers a full SQL expression
|
||||
language that allows Pythonic construction
|
||||
of SQL constructs that render directly to SQL
|
||||
strings for a target database, returning
|
||||
result sets that are essentially enhanced DBAPI
|
||||
cursors.
|
||||
* Transactions should be the norm. With SQLAlchemy's
|
||||
ORM, nothing goes to permanent storage until
|
||||
commit() is called. SQLAlchemy encourages applications
|
||||
to create a consistent means of delineating
|
||||
the start and end of a series of operations.
|
||||
* Never render a literal value in a SQL statement.
|
||||
Bound parameters are used to the greatest degree
|
||||
possible, allowing query optimizers to cache
|
||||
query plans effectively and making SQL injection
|
||||
attacks a non-issue.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Latest documentation is at:
|
||||
|
||||
http://www.sqlalchemy.org/docs/
|
||||
|
||||
Installation / Requirements
|
||||
---------------------------
|
||||
|
||||
Full documentation for installation is at
|
||||
`Installation <http://www.sqlalchemy.org/docs/intro.html#installation>`_.
|
||||
|
||||
Getting Help / Development / Bug reporting
|
||||
------------------------------------------
|
||||
|
||||
Please refer to the `SQLAlchemy Community Guide <http://www.sqlalchemy.org/support.html>`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
SQLAlchemy is distributed under the `MIT license
|
||||
<http://www.opensource.org/licenses/mit-license.php>`_.
|
||||
|
||||
|
||||
|
376
deps/SQLAlchemy-1.0.15.dist-info/RECORD
vendored
Normal file
376
deps/SQLAlchemy-1.0.15.dist-info/RECORD
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
SQLAlchemy-1.0.15.dist-info/DESCRIPTION.rst,sha256=ZN8fj2owI_rw0Emr3_RXqoNfTFkThjiZy7xcCzg1W_g,5013
|
||||
SQLAlchemy-1.0.15.dist-info/METADATA,sha256=PXrsCp9uV7PWUcEVkGZfGxqVJ9hPqrmf4bF01OegS74,5786
|
||||
SQLAlchemy-1.0.15.dist-info/RECORD,,
|
||||
SQLAlchemy-1.0.15.dist-info/WHEEL,sha256=qlXKyUxaKdKGVZKHdQaMFOPNWg5qC8OVyb-aicJwJ6U,104
|
||||
SQLAlchemy-1.0.15.dist-info/metadata.json,sha256=6ZjUYl1zEtn3LvCDChujq-h7i3ttPnXQ-Qa0ZTVQ3Ps,965
|
||||
SQLAlchemy-1.0.15.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
|
||||
sqlalchemy/__init__.py,sha256=TXDtb6mvt-pw89t8oryfyzN1O_-tLB008zsHbspt6A0,2129
|
||||
sqlalchemy/cprocessors.cpython-34m.so,sha256=il80Q4miLp0Jrhy98MHAXTV8TATdlZVuY9GuoKo_fzQ,34448
|
||||
sqlalchemy/cresultproxy.cpython-34m.so,sha256=WtHt8ezIDtS12UIvsrMDudamGPk4AMdc10HXsXLcItU,42664
|
||||
sqlalchemy/cutils.cpython-34m.so,sha256=UAduuJiZ7iABA3VMMcdmp-FqqgIm-XmbjgZhtITrrzI,19276
|
||||
sqlalchemy/events.py,sha256=4gPVD8hBxWEUrzMpGiNcmDrpCMRaSqvps04offK-fVo,43942
|
||||
sqlalchemy/exc.py,sha256=NhA5R5nDdducWkp0MXtlQ0-Q6iF_rhqkHWblIfuSYGk,11706
|
||||
sqlalchemy/inspection.py,sha256=zMa-2nt-OQ0Op1dqq0Z2XCnpdAFSTkqif5Kdi8Wz8AU,3093
|
||||
sqlalchemy/interfaces.py,sha256=XSx5y-HittAzc79lU4C7rPbTtSW_Hc2c89NqCy50tsQ,10967
|
||||
sqlalchemy/log.py,sha256=opX7UORq5N6_jWxN9aHX9OpiirwAcRA0qq-u5m4SMkQ,6712
|
||||
sqlalchemy/pool.py,sha256=-F51TIJYl0XGTV2_sdpV8C1m0jTTQaq0nAezdmSgr84,47220
|
||||
sqlalchemy/processors.py,sha256=Li1kdC-I0v03JxeOz4V7u4HAevK6LledyCPvaL06mYc,5220
|
||||
sqlalchemy/schema.py,sha256=af2xp2pyTsbPLrSIBr4aOjc25bjnqku_uXL9pqEltx8,1200
|
||||
sqlalchemy/types.py,sha256=qcoy5xKaurDV4kaXr489GL2sz8FKkWX21Us3ZCqeasg,1650
|
||||
sqlalchemy/connectors/__init__.py,sha256=97YbriYu5mcljh7opc1JOScRlf3Tk8ldbn5urBVm4WY,278
|
||||
sqlalchemy/connectors/mxodbc.py,sha256=4giS6L3cIe3JBtTAg1pZpxuIJKd7xV1Cq7YUf5Mea-8,5347
|
||||
sqlalchemy/connectors/pyodbc.py,sha256=pG2yf3cEDtTr-w_m4to6jF5l8hZk6MJv69K3cg84NfY,6264
|
||||
sqlalchemy/connectors/zxJDBC.py,sha256=2KK_sVSgMsdW0ufZqAwgXjd1FsMb4hqbiUQRAkM0RYg,1868
|
||||
sqlalchemy/databases/__init__.py,sha256=BaQyAuMjXNpZYV47hCseHrDtPzTfSw-iqUQYxMWJddw,817
|
||||
sqlalchemy/dialects/__init__.py,sha256=7SMul8PL3gkbJRUwAwovHLae5qBBApRF-VcRwU-VtdU,1012
|
||||
sqlalchemy/dialects/postgres.py,sha256=heNVHys6E91DIBepXT3ls_4_6N8HTTahrZ49W5IR3M0,614
|
||||
sqlalchemy/dialects/firebird/__init__.py,sha256=QYmQ0SaGfq3YjDraCV9ALwqVW5A3KDUF0F6air_qp3Q,664
|
||||
sqlalchemy/dialects/firebird/base.py,sha256=IT0prWkh1TFSTke-BqGdVMGdof53zmWWk6zbJZ_TuuI,28170
|
||||
sqlalchemy/dialects/firebird/fdb.py,sha256=l4s6_8Z0HvqxgqGz0LNcKWP1qUmEc3M2XM718_drN34,4325
|
||||
sqlalchemy/dialects/firebird/kinterbasdb.py,sha256=kCsn2ed4u9fyjcyfEI3rXQdKvL05z9wtf5YjW9-NrvI,6299
|
||||
sqlalchemy/dialects/mssql/__init__.py,sha256=G12xmirGZgMzfUKZCA8BFfaCmqUDuYca9Fu2VP_eaks,1081
|
||||
sqlalchemy/dialects/mssql/adodbapi.py,sha256=dHZgS3pEDX39ixhlDfTtDcjCq6rdjF85VS7rIZ1TfYo,2493
|
||||
sqlalchemy/dialects/mssql/base.py,sha256=FQ9DNxTBqnFInTNmffcmZeySpj-_53l_aQFxNWB2ljY,68991
|
||||
sqlalchemy/dialects/mssql/information_schema.py,sha256=pwuTsgOCY5eSBW9w-g-pyJDRfyuZ_rOEXXNYRuAroCE,6418
|
||||
sqlalchemy/dialects/mssql/mxodbc.py,sha256=G9LypIeEizgxeShtDu2M7Vwm8NopnzaTmnZMD49mYeg,3856
|
||||
sqlalchemy/dialects/mssql/pymssql.py,sha256=fQE2el6WDwm8EeFqNn9qYXyw_oFPFqidA2zd1fXs8G0,3080
|
||||
sqlalchemy/dialects/mssql/pyodbc.py,sha256=LAamdDoPAMSTa0I-51PlJ_sVvyM5M4f99XQcz9mMZR8,9653
|
||||
sqlalchemy/dialects/mssql/zxjdbc.py,sha256=u4uBgwk0LbI7_I5CIvM3C4bBb0pmrw2_DqRh_ehJTkI,2282
|
||||
sqlalchemy/dialects/mysql/__init__.py,sha256=3cQ2juPT8LsZTicPa2J-0rCQjQIQaPgyBzxjV3O_7xs,1171
|
||||
sqlalchemy/dialects/mysql/base.py,sha256=HZA1lxTNHYRVxeMeAw8RwJz2lYbRRYY1qJ5J_SujC9Q,123316
|
||||
sqlalchemy/dialects/mysql/cymysql.py,sha256=nqsdQA8LBLIc6eilgX6qwkjm7szsUoqMTVYwK9kkfsE,2349
|
||||
sqlalchemy/dialects/mysql/gaerdbms.py,sha256=2MxtTsIqlpq_J32HHqDzz-5vu-mC51Lb7PvyGkJa73M,3387
|
||||
sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=K7HvQzLUZGm_pIL2ppltfjGIHPsVlr6R-F50dMg2V7g,6106
|
||||
sqlalchemy/dialects/mysql/mysqldb.py,sha256=McqROngxAknbLOXoUAG9o9mP9FQBLs-ouD-JqqI2Ses,6564
|
||||
sqlalchemy/dialects/mysql/oursql.py,sha256=rmdr-r66iJ2amqFeGvCohvE8WCl_i6R9KcgVG0uXOQs,8124
|
||||
sqlalchemy/dialects/mysql/pymysql.py,sha256=e-qehI-sASmAjEa0ajHqjZjlyJYWsb3RPQY4iBR5pz0,1504
|
||||
sqlalchemy/dialects/mysql/pyodbc.py,sha256=Ze9IOKw6ANVQj25IlmSGR8aaJhM0pMuRtbzKF7UsZCY,2665
|
||||
sqlalchemy/dialects/mysql/zxjdbc.py,sha256=LIhe2mHSRVgi8I7qmiTMVBRSpuWJVnuDtpHTUivIx0M,3942
|
||||
sqlalchemy/dialects/oracle/__init__.py,sha256=UhF2ZyPfT3EFAnP8ZjGng6GnWSzmAkjMax0Lucpn0Bg,797
|
||||
sqlalchemy/dialects/oracle/base.py,sha256=9EnyRy5oRf528Njj1dHohB8_1Gh3FivcLcSovH8w7Q0,56790
|
||||
sqlalchemy/dialects/oracle/cx_oracle.py,sha256=rQPBYvlS0KZIcw4Pg1ARlGKdmOUcR0xsGge0CXVhxfs,38765
|
||||
sqlalchemy/dialects/oracle/zxjdbc.py,sha256=nC7XOCY3NdTLrEyIacNTnLDCaeVjWn59q8UYssJL8Wo,8112
|
||||
sqlalchemy/dialects/postgresql/__init__.py,sha256=SjCtM5b3EaGyRaTyg_i82sh_qjkLEIVUXW91XDihiCM,1299
|
||||
sqlalchemy/dialects/postgresql/base.py,sha256=SJ_gCtLwlrhR-h02Vi4dn5bNI1eHdZXTZKf1nBwXJ8g,104340
|
||||
sqlalchemy/dialects/postgresql/constraints.py,sha256=8UDx_2TNQgqIUSRETZPhgninJigQ6rMfdRNI6vIt3Is,3119
|
||||
sqlalchemy/dialects/postgresql/hstore.py,sha256=n8Wsd7Uldk3bbg66tTa0NKjVqjhJUbF1mVeUsM7keXA,11402
|
||||
sqlalchemy/dialects/postgresql/json.py,sha256=MTlIGinMDa8iaVbZMOzYnremo0xL4tn2wyGTPwnvX6U,12215
|
||||
sqlalchemy/dialects/postgresql/pg8000.py,sha256=x6o3P8Ad0wKsuF9qeyip39BKc5ORJZ4nWxv-8qOdj0E,8375
|
||||
sqlalchemy/dialects/postgresql/psycopg2.py,sha256=Z6ubvg7bzVBBiyTebyvf1WGX4MgJlryRHHzyLQp3qEU,27019
|
||||
sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=8R3POkJH8z8a2DxwKNmfmQOsxFqsg4tU_OnjGj3OfDA,1651
|
||||
sqlalchemy/dialects/postgresql/pypostgresql.py,sha256=raQRfZb8T9-c-jmq1w86Wci5QyiXgf_9_71OInT_sAw,2655
|
||||
sqlalchemy/dialects/postgresql/ranges.py,sha256=MihdGXMdmCM6ToIlrj7OJx9Qh_8BX8bv5PSaAepHmII,4814
|
||||
sqlalchemy/dialects/postgresql/zxjdbc.py,sha256=AhEGRiAy8q-GM0BStFcsLBgSwjxHkkwy2-BSroIoADo,1397
|
||||
sqlalchemy/dialects/sqlite/__init__.py,sha256=0wW0VOhE_RtFDpRcbwvvo3XtD6Y2-SDgG4K7468eh_w,736
|
||||
sqlalchemy/dialects/sqlite/base.py,sha256=_L9-854ITf8Fl2BgUymF9fKjDFvXSo7Pb2yuz1CMkDo,55007
|
||||
sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=sgXCqn8ZtNIeTDwyo253Kj5mn4TPlIW3AZCNNmURi2A,4129
|
||||
sqlalchemy/dialects/sqlite/pysqlite.py,sha256=G-Cg-iI-ErYsVjOH4UlQTEY9pLnLOLV89ik8q0-reuY,14980
|
||||
sqlalchemy/dialects/sybase/__init__.py,sha256=gwCgFR_C_hoj0Re7PiaW3zmKSWaLpsd96UVXdM7EnTM,894
|
||||
sqlalchemy/dialects/sybase/base.py,sha256=Xpl3vEd5VDyvoIRMg0DZa48Or--yBSrhaZ2CbTSCt0w,28853
|
||||
sqlalchemy/dialects/sybase/mxodbc.py,sha256=E_ask6yFSjyhNPvv7gQsvA41WmyxbBvRGWjCyPVr9Gs,901
|
||||
sqlalchemy/dialects/sybase/pyodbc.py,sha256=0a_gKwrIweJGcz3ZRYuQZb5BIvwjGmFEYBo9wGk66kI,2102
|
||||
sqlalchemy/dialects/sybase/pysybase.py,sha256=tu2V_EbtgxWYOvt-ybo5_lLiBQzsIFaAtF8e7S1_-rk,3208
|
||||
sqlalchemy/engine/__init__.py,sha256=orab-ubkvGHzmhExRx2e6zg1hvNOiF1AU-i48xMqcvc,18837
|
||||
sqlalchemy/engine/base.py,sha256=HryAJKI1-jeViL8WVNFSjvBWfT9G20wUFRpcybW373s,79395
|
||||
sqlalchemy/engine/default.py,sha256=U_yaliCazUHp6cfk_NVzhB4F_zOJSyy959rHyk40J4M,36548
|
||||
sqlalchemy/engine/interfaces.py,sha256=CmPYM_oDp1zAPH13sKmufO4Tuha6KA-fXRQq-K_3YTE,35908
|
||||
sqlalchemy/engine/reflection.py,sha256=jk5_oCt6oFbwznVTGXkhgj7L9xUziL1cMsuvTLyafUA,28653
|
||||
sqlalchemy/engine/result.py,sha256=Qh8w8mbOPcXPpVoMfwLkBts0gT-_6LASYsCZ6BuvnJA,44360
|
||||
sqlalchemy/engine/strategies.py,sha256=mwy-CTrnXzyaIA1TRQBQ_Z2O8wN0lnTNZwDefEWCR9A,8929
|
||||
sqlalchemy/engine/threadlocal.py,sha256=y4wOLjtbeY-dvp2GcJDtos6F2jzfP11JVAaSFwZ0zRM,4191
|
||||
sqlalchemy/engine/url.py,sha256=ZhS_Iqiu6V1kfIM2pcv3ud9fOPXkFOHBv8wiLOqbJhc,8228
|
||||
sqlalchemy/engine/util.py,sha256=Tvb9sIkyd6qOwIA-RsBmo5j877UXa5x-jQmhqnhHWRA,2338
|
||||
sqlalchemy/event/__init__.py,sha256=KnUVp-NVX6k276ntGffxgkjVmIWR22FSlzrbAKqQ6S4,419
|
||||
sqlalchemy/event/api.py,sha256=O2udbj5D7HdXcvsGBQk6-dK9CAFfePTypWOrUdqmhYY,5990
|
||||
sqlalchemy/event/attr.py,sha256=VfRJJl4RD24mQaIoDwArWL2hsGOX6ISSU6vKusVMNO0,12053
|
||||
sqlalchemy/event/base.py,sha256=DWDKZV19fFsLavu2cXOxXV8NhO3XuCbKcKamBKyXuME,9540
|
||||
sqlalchemy/event/legacy.py,sha256=ACnVeBUt8uwVfh1GNRu22cWCADC3CWZdrsBKzAd6UQQ,5814
|
||||
sqlalchemy/event/registry.py,sha256=13wx1qdEmcQeCoAmgf_WQEMuR43h3v7iyd2Re54QdOE,7786
|
||||
sqlalchemy/ext/__init__.py,sha256=smCZIGgjJprT4ddhuYSLZ8PrTn4NdXPP3j03a038SdE,322
|
||||
sqlalchemy/ext/associationproxy.py,sha256=y61Y4UIZNBit5lqk2WzdHTCXIWRrBg3hHbRVsqXjnqE,33422
|
||||
sqlalchemy/ext/automap.py,sha256=JXZ4SgtnU0SuNMxs9iwWpBf1WAd44HDS0u1skn3wWKI,41566
|
||||
sqlalchemy/ext/baked.py,sha256=1ny-NbAxFbg82U8RxJLMqZGg-yBP7I5cOIZkO36XsX0,17000
|
||||
sqlalchemy/ext/compiler.py,sha256=aSSlySoTsqN-JkACWFIhv3pq2CuZwxKm6pSDfQoc10Q,16257
|
||||
sqlalchemy/ext/horizontal_shard.py,sha256=XEBYIfs0YrTt_2vRuaBY6C33ZOZMUHQb2E4X2s3Szns,4814
|
||||
sqlalchemy/ext/hybrid.py,sha256=wNXvuYEEmKy-Nc6z7fu1c2gNWCMOiQA0N14Y3FCq5lo,27989
|
||||
sqlalchemy/ext/instrumentation.py,sha256=HRgNiuYJ90_uSKC1iDwsEl8_KXscMQkEb9KeElk-yLE,14856
|
||||
sqlalchemy/ext/mutable.py,sha256=QvIb2-tLqgR3Xdq94y8zJqX9cxfIUgJ5K7aDV6HIlww,25440
|
||||
sqlalchemy/ext/orderinglist.py,sha256=UCkuZxTWAQ0num-b5oNm8zNJAmVuIFcbFXt5e7JPx-U,13816
|
||||
sqlalchemy/ext/serializer.py,sha256=fK3N1miYF16PSIZDjLFS2zI7y-scZ9qtmopXIfzPqrA,5586
|
||||
sqlalchemy/ext/declarative/__init__.py,sha256=Jpwf2EukqwNe4RzDfCmX1p-hQ6pPhJEIL_xunaER3tw,756
|
||||
sqlalchemy/ext/declarative/api.py,sha256=SHjIAi5ObpEy-NOZhlsWeVscEMmn9qgc187TRi9jR_8,23322
|
||||
sqlalchemy/ext/declarative/base.py,sha256=ZVSQ-6ifPKpnSyoD4OjZk_oJUkgMPdRGi-Obbn6C6MM,25290
|
||||
sqlalchemy/ext/declarative/clsregistry.py,sha256=jaLLSr-66XvLnA1Z9kxjKatH_XHxWchqEXMKwvjKAXk,10817
|
||||
sqlalchemy/orm/__init__.py,sha256=UzDockQEVMaWvr-FE4y1rptrMb5uX5k8v_UNQs82qFY,8033
|
||||
sqlalchemy/orm/attributes.py,sha256=OmXkppJEZxRGc0acZZZkSbUhdfDl8ry3Skmvzl3OtLQ,56510
|
||||
sqlalchemy/orm/base.py,sha256=nS21na3Yx76UJzhWjzPLud1Ny0Xbmqx2DZQpVpHxHQM,14668
|
||||
sqlalchemy/orm/collections.py,sha256=TFutWIn_c07DI48FDOKMsFMnAoQB3BG2FnEMGzEF3iI,53549
|
||||
sqlalchemy/orm/dependency.py,sha256=phB8nS1788FSd4dWa2j9d4uj6QFlRL7nzcXvh3Bb7Zo,46192
|
||||
sqlalchemy/orm/deprecated_interfaces.py,sha256=A63t6ivbZB3Wq8vWgL8I05uTRR6whcWnIPkquuTIPXU,18254
|
||||
sqlalchemy/orm/descriptor_props.py,sha256=uk5r77w1VUWVgn0bkgOItkAlMh9FRgeT6OCgOHz3_bM,25141
|
||||
sqlalchemy/orm/dynamic.py,sha256=I_YP7X-H9HLjeFHmYgsOas6JPdqg0Aqe0kaltt4HVzA,13283
|
||||
sqlalchemy/orm/evaluator.py,sha256=o9E_mF3gPRa9HF_pNu-5twDe7865eFgO1FSCfoUB71s,4813
|
||||
sqlalchemy/orm/events.py,sha256=yRaoXlBL78b3l11itTrAy42UhLu42-7cgXKCFUGNXSg,69410
|
||||
sqlalchemy/orm/exc.py,sha256=P5lxi5RMFokiHL136VBK0AP3UmAlJcSDHtzgo-M6Kgs,5439
|
||||
sqlalchemy/orm/identity.py,sha256=zsb8xOZaPYKvs4sGhyxW21mILQDrtdSuzD4sTyeKdJs,9021
|
||||
sqlalchemy/orm/instrumentation.py,sha256=xtq9soM3mpMws7xqNJIFYXqKw65p2nnxCTfmMpuvpeI,17510
|
||||
sqlalchemy/orm/interfaces.py,sha256=AqitvZ_BBkB6L503uhdH55nxHplleJ2kQMwM7xKq9Sc,21552
|
||||
sqlalchemy/orm/loading.py,sha256=ZlxQszfG776WPVd5EHzPMdYat5IgmFltQ7QErMU3dtI,22885
|
||||
sqlalchemy/orm/mapper.py,sha256=LKnaVLyDk4AjTdANzDq03R2XyI-SlkSsT50dUaOlsqI,115131
|
||||
sqlalchemy/orm/path_registry.py,sha256=8Pah0P8yPVUyRjoET7DvIMGtM5PC8HZJC4GtxAyqVAs,8370
|
||||
sqlalchemy/orm/persistence.py,sha256=WzUUNm1UGm5mGxbv94hLTQowEDNoXfU1VoyGnoKeN_g,51028
|
||||
sqlalchemy/orm/properties.py,sha256=HR3eoY3Ze3FUPPNCXM_FruWz4pEMWrGlqtCGiK2G1qE,10426
|
||||
sqlalchemy/orm/query.py,sha256=frir4h863dRbadKCdys5XeBClZ-SDcvupVgKLaN6Dlo,148267
|
||||
sqlalchemy/orm/relationships.py,sha256=79LRGGz8MxsKsAlv0vuZ6MYZXzDXXtfiOCZg-IQ9hiU,116992
|
||||
sqlalchemy/orm/scoping.py,sha256=Ao-K4iqg4pBp7Si5JOAlro5zUL_r500TC3lVLcFMLDs,6421
|
||||
sqlalchemy/orm/session.py,sha256=yctpvCsLUcFv9Sy8keT1SElZ2VH5DNScYtO7Z77ptYI,111314
|
||||
sqlalchemy/orm/state.py,sha256=4LwwftOtPQldH12SKZV2UFgzqPOCj40QfQ08knZs0_E,22984
|
||||
sqlalchemy/orm/strategies.py,sha256=wvl6-ZwL28YJsJPY6xuHAYYnjk8k9fAbYqupvfvQ_Xo,58947
|
||||
sqlalchemy/orm/strategy_options.py,sha256=LqJWCzML6YCI_toThom_bvfZQAOj6WIb3MrO9K7K6bo,34974
|
||||
sqlalchemy/orm/sync.py,sha256=B-d-H1Gzw1TkflpvgJeQghwTzqObzhZCQdvEdSPyDeE,5451
|
||||
sqlalchemy/orm/unitofwork.py,sha256=EQvZ7RZ-u5wJT51BWTeMJJi-tt22YRnmqywGUCn0Qrc,23343
|
||||
sqlalchemy/orm/util.py,sha256=hMlZbgA8Qji9--EAahwzHsyKeR_GCEKwFZWjR4kgPsk,38077
|
||||
sqlalchemy/sql/__init__.py,sha256=IFCJYIilmmAQRnSDhv9Y6LQUSpx6pUU5zp9VT7sOx0c,1737
|
||||
sqlalchemy/sql/annotation.py,sha256=8ncgAVUo5QCoinApKjREi8esWNMFklcBqie8Q42KsaQ,6136
|
||||
sqlalchemy/sql/base.py,sha256=TuXOp7z0Q30qKAjhgcsts6WGvRbvg6F7OBojMQAxjX0,20990
|
||||
sqlalchemy/sql/compiler.py,sha256=4szeiIUoO6kgj37d8skkDVdPJw5ZxYW6KmyTDDmnK3U,100569
|
||||
sqlalchemy/sql/crud.py,sha256=b-o2vT2CV2hIxdky9NpzvgEMHjbKxvF4tMgGdU4mLvs,19837
|
||||
sqlalchemy/sql/ddl.py,sha256=nkjd_B4lKwC2GeyPjE0ZtRB9RKXccQL1g1XoZ4p69sM,37540
|
||||
sqlalchemy/sql/default_comparator.py,sha256=NqvS_PgULqSaeJR25IjB2mUMy0R2O8avX0i9ge-gDi4,10464
|
||||
sqlalchemy/sql/dml.py,sha256=7846H52IMJfMYi5Jd-Cv6Hy9hZM4dkonXbjfBjl5ED4,33330
|
||||
sqlalchemy/sql/elements.py,sha256=okDQjYYhPucEX5OmP6XFxWUauFLrpV3ucwIisTSVVGE,133812
|
||||
sqlalchemy/sql/expression.py,sha256=vFZ9MmBlC9Fg8IYzLMAwXgcsnXZhkZbUstY6dO8BFGY,5833
|
||||
sqlalchemy/sql/functions.py,sha256=CV-L1qZDfNx378--oh_g6I7BQomMfDrjOmwNT6JxkAA,18669
|
||||
sqlalchemy/sql/naming.py,sha256=foE2lAzngLCFXCeHrpv0S4zT23GCnZLCiata2MPo0kE,4662
|
||||
sqlalchemy/sql/operators.py,sha256=UeZgb7eRhWd4H7OfJZkx0ZWOjvo5chIUXQsBAIeeTDY,23013
|
||||
sqlalchemy/sql/schema.py,sha256=df0R2rXhjwCkKGHDshHy_mdrYJvWQ27OjAqhvk-xy9E,147681
|
||||
sqlalchemy/sql/selectable.py,sha256=BT5RdjMrV026gLX1ThF_q1NFtEr2Op78yS-OUi3SYsc,119573
|
||||
sqlalchemy/sql/sqltypes.py,sha256=JGxizqIjO1WFuZpppWj1Yi5cvCyBczb1JqUQeuhQn8s,54879
|
||||
sqlalchemy/sql/type_api.py,sha256=LDXlmstH0rIVosJy2KioqRq2eO25EHV9NJeLlKuZD88,42846
|
||||
sqlalchemy/sql/util.py,sha256=GhTktynNUK9LROR9YYSO0idy6mu6riDUkm-gt8bkfYI,20629
|
||||
sqlalchemy/sql/visitors.py,sha256=4ipGvAkqFaSAWgyNuKjx5x_ms8GIy9aq-wC5pj4-Z3g,10271
|
||||
sqlalchemy/testing/__init__.py,sha256=MwKimX0atzs_SmG2j74GXLiyI8O56e3DLq96tcoL0TM,1095
|
||||
sqlalchemy/testing/assertions.py,sha256=r1I2nHC599VZcY-5g0JYRQl8bl9kjkf6WFOooOmJ2eE,16112
|
||||
sqlalchemy/testing/assertsql.py,sha256=-fP9Iuhdu52BJoT1lEj_KED8jy5ay_XiJu7i4Ry9eWA,12335
|
||||
sqlalchemy/testing/config.py,sha256=nqvVm55Vk0BVNjk1Wj3aYR65j_EEEepfB-W9QSFLU-k,2469
|
||||
sqlalchemy/testing/distutils_run.py,sha256=tkURrZRwgFiSwseKm1iJRkSjKf2Rtsb3pOXRWtACTHI,247
|
||||
sqlalchemy/testing/engines.py,sha256=u6GlDMXt0FKqVTQe_QJ5JXAnkA6W-xdw6Fe_5gMAQhg,9359
|
||||
sqlalchemy/testing/entities.py,sha256=IXqTgAihV-1TZyxL0MWdZzu4rFtxdbWKWFetIJWNGM4,2992
|
||||
sqlalchemy/testing/exclusions.py,sha256=WuH_tVK5fZJWe8Hu2LzNB4HNQMa_iAUaGC-_6mHUdIM,12570
|
||||
sqlalchemy/testing/fixtures.py,sha256=q4nK-81z2EWs17TjeJtPmnaJUCtDdoUiIU7jgLq3l_w,10721
|
||||
sqlalchemy/testing/mock.py,sha256=vj5q-GzJrLW6mMVDLqsppxBu_p7K49VvjfiVt5tn0o8,630
|
||||
sqlalchemy/testing/pickleable.py,sha256=8I8M4H1XN29pZPMxZdYkmpKWfwzPsUn6WK5FX4UP9L4,2641
|
||||
sqlalchemy/testing/profiling.py,sha256=Q_wOTS5JtcGBcs2eCYIvoRoDS_FW_HcfEW3hXWB87Zg,8392
|
||||
sqlalchemy/testing/provision.py,sha256=DW1OD8lXK4mv0E0ibTPJoq_TwjBSNFPzrk0OktbZXDw,9388
|
||||
sqlalchemy/testing/replay_fixture.py,sha256=iAxg7XsFkKSCcJnrNPQNJfjMxOgeBAa-ShOkywWPJ4w,5429
|
||||
sqlalchemy/testing/requirements.py,sha256=aIdvbfugMzrlVdldEbpcwretX-zjiukPhPUSZgulrzU,19949
|
||||
sqlalchemy/testing/runner.py,sha256=hpNH6MNTif4TnBRySxpm92KgFwDK0mOa8eF7wZXumTI,1607
|
||||
sqlalchemy/testing/schema.py,sha256=agOzrIMvmuUCeVZY5mYjJ1eJmOP69-wa0gZALtNtJBk,3446
|
||||
sqlalchemy/testing/util.py,sha256=IJ688AWzichtXVwWgYf_A4BUbcXPGsK6BQP5fvY3h-U,7544
|
||||
sqlalchemy/testing/warnings.py,sha256=-KskRAh1RkJ_69UIY_WR7i15u21U3gDLQ6nKlnJT7_w,987
|
||||
sqlalchemy/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/testing/plugin/bootstrap.py,sha256=Iw8R-d1gqoz_NKFtPyGfdX56QPcQHny_9Lvwov65aVY,1634
|
||||
sqlalchemy/testing/plugin/noseplugin.py,sha256=In79x6zs9DOngfoYpaHojihWlSd4PeS7Nwzh3M_KNM4,2847
|
||||
sqlalchemy/testing/plugin/plugin_base.py,sha256=bZvCglPhWVY6dyrkWFEjuJ3ccjt3VdRvbjE6oeNil8o,17335
|
||||
sqlalchemy/testing/plugin/pytestplugin.py,sha256=o29RaERGgyakyA3ovnGzCq7rPBSjp6Gjf31Yy9jLw-4,6146
|
||||
sqlalchemy/testing/suite/__init__.py,sha256=wqCTrb28i5FwhQZOyXVlnz3mA94iQOUBio7lszkFq-g,471
|
||||
sqlalchemy/testing/suite/test_ddl.py,sha256=Baw0ou9nKdADmrRuXgWzF1FZx0rvkkw3JHc6yw5BN0M,1838
|
||||
sqlalchemy/testing/suite/test_dialect.py,sha256=ORQPXUt53XtO-5ENlWgs8BpsSdPBDjyMRl4W2UjXLI4,1165
|
||||
sqlalchemy/testing/suite/test_insert.py,sha256=nP0mgVpsVs72MHMADmihB1oXLbFBpsYsLGO3BlQ7RLU,8132
|
||||
sqlalchemy/testing/suite/test_reflection.py,sha256=HtJRsJ_vuNMrOhnPTvuIvRg66OakSaSpeCU36zhaSPg,24616
|
||||
sqlalchemy/testing/suite/test_results.py,sha256=oAcO1tD0I7c9ErMeSvSZBZfz1IBDMJHJTf64Y1pBodk,6685
|
||||
sqlalchemy/testing/suite/test_select.py,sha256=u0wAz1g-GrAFdZpG4zwSrVckVtjULvjlbd0Z1U1jHAA,5729
|
||||
sqlalchemy/testing/suite/test_sequence.py,sha256=fmBR4Pc5tOLSkXFxfcqwGx1z3xaxeJeUyqDnTakKTBU,3831
|
||||
sqlalchemy/testing/suite/test_types.py,sha256=UKa-ZPdpz16mVKvT-9ISRAfqdrqiKaE7IA-_phQQuxo,17088
|
||||
sqlalchemy/testing/suite/test_update_delete.py,sha256=r5p467r-EUsjEcWGfUE0VPIfN4LLXZpLRnnyBLyyjl4,1582
|
||||
sqlalchemy/util/__init__.py,sha256=G06a5vBxg27RtWzY6dPZHt1FO8qtOiy_2C9PHTTMblI,2520
|
||||
sqlalchemy/util/_collections.py,sha256=s41rf2YrYEfl6MTIp3x3WMzLvGP532PR9FD8-2VwivA,27889
|
||||
sqlalchemy/util/compat.py,sha256=gInErUyI0XdS590SIFSbjdmIdwn-hxFVyWYU12p_QqM,6873
|
||||
sqlalchemy/util/deprecations.py,sha256=D_LTsfb9jHokJtPEWNDRMJOc372xRGNjputAiTIysRU,4403
|
||||
sqlalchemy/util/langhelpers.py,sha256=Nhe3Y9ieK6JaFYejjYosVOjOSSIBT2V385Hu6HGcyZk,41607
|
||||
sqlalchemy/util/queue.py,sha256=rs3W0LDhKt7M_dlQEjYpI9KS-bzQmmwN38LE_-RRVvU,6548
|
||||
sqlalchemy/util/topological.py,sha256=xKsYjjAat4p8cdqRHKwibLzr6WONbPTC0X8Mqg7jYno,2794
|
||||
SQLAlchemy-1.0.15.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
sqlalchemy/testing/__pycache__/entities.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-34.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/mxodbc.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/compiler.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/log.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/distutils_run.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_types.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/events.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/interfaces.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/state.cpython-34.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/api.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/mock.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/crud.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/evaluator.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/interfaces.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/util.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/elements.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/provision.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/deprecated_interfaces.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertions.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/scoping.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/mutable.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/zxjdbc.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/requirements.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/zxJDBC.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/fixtures.cpython-34.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/event/__pycache__/legacy.cpython-34.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/path_registry.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertsql.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/zxjdbc.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/identity.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/processors.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/langhelpers.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_insert.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/_collections.cpython-34.pyc,,
|
||||
sqlalchemy/event/__pycache__/api.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/exc.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/collections.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/exclusions.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/horizontal_shard.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/json.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/util.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/pyodbc.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/util.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/hybrid.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/pool.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/strategies.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/schema.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/mxodbc.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/unitofwork.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/operators.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/ddl.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategies.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/url.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/config.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/properties.cpython-34.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/clsregistry.cpython-34.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/noseplugin.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-34.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/mapper.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/__pycache__/postgres.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/kinterbasdb.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/reflection.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/naming.cpython-34.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/default.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/orderinglist.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/adodbapi.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/types.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/sqltypes.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/annotation.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/selectable.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/baked.cpython-34.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/functions.cpython-34.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/zxjdbc.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/serializer.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dependency.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pypostgresql.cpython-34.pyc,,
|
||||
sqlalchemy/event/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/type_api.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/mxodbc.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/relationships.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/events.cpython-34.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/associationproxy.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/zxjdbc.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/interfaces.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/inspection.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-34.pyc,,
|
||||
sqlalchemy/event/__pycache__/attr.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/result.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/sync.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/loading.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/pysybase.cpython-34.pyc,,
|
||||
sqlalchemy/event/__pycache__/registry.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/schema.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/threadlocal.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/default_comparator.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategy_options.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/fdb.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/runner.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_select.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/engines.cpython-34.pyc,,
|
||||
sqlalchemy/engine/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/session.cpython-34.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/pyodbc.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/deprecations.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_results.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/constraints.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/instrumentation.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/schema.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/dml.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/topological.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/oursql.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/compiler.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/base.cpython-34.pyc,,
|
||||
sqlalchemy/ext/__pycache__/automap.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/compat.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/query.cpython-34.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dynamic.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/descriptor_props.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/replay_fixture.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/warnings.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/__pycache__/exc.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/util.cpython-34.pyc,,
|
||||
sqlalchemy/util/__pycache__/queue.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/pickleable.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/attributes.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/expression.cpython-34.pyc,,
|
||||
sqlalchemy/databases/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-34.pyc,,
|
||||
sqlalchemy/event/__pycache__/__init__.cpython-34.pyc,,
|
||||
sqlalchemy/testing/__pycache__/profiling.cpython-34.pyc,,
|
||||
sqlalchemy/sql/__pycache__/visitors.cpython-34.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/gaerdbms.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/instrumentation.cpython-34.pyc,,
|
||||
sqlalchemy/orm/__pycache__/persistence.cpython-34.pyc,,
|
5
deps/SQLAlchemy-1.0.15.dist-info/WHEEL
vendored
Normal file
5
deps/SQLAlchemy-1.0.15.dist-info/WHEEL
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp34-cp34m-linux_armv7l
|
||||
|
1
deps/SQLAlchemy-1.0.15.dist-info/metadata.json
vendored
Normal file
1
deps/SQLAlchemy-1.0.15.dist-info/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: Jython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database :: Front-Ends", "Operating System :: OS Independent"], "extensions": {"python.details": {"contacts": [{"email": "mike_mp@zzzcomputing.com", "name": "Mike Bayer", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://www.sqlalchemy.org"}}}, "generator": "bdist_wheel (0.29.0)", "license": "MIT License", "metadata_version": "2.0", "name": "SQLAlchemy", "summary": "Database Abstraction Library", "test_requires": [{"requires": ["mock", "pytest (>=2.5.2)", "pytest-xdist"]}], "version": "1.0.15"}
|
1
deps/SQLAlchemy-1.0.15.dist-info/top_level.txt
vendored
Normal file
1
deps/SQLAlchemy-1.0.15.dist-info/top_level.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
sqlalchemy
|
54
deps/Werkzeug-0.11.11.dist-info/DESCRIPTION.rst
vendored
Normal file
54
deps/Werkzeug-0.11.11.dist-info/DESCRIPTION.rst
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
Werkzeug
|
||||
========
|
||||
|
||||
Werkzeug started as simple collection of various utilities for WSGI
|
||||
applications and has become one of the most advanced WSGI utility
|
||||
modules. It includes a powerful debugger, full featured request and
|
||||
response objects, HTTP utilities to handle entity tags, cache control
|
||||
headers, HTTP dates, cookie handling, file uploads, a powerful URL
|
||||
routing system and a bunch of community contributed addon modules.
|
||||
|
||||
Werkzeug is unicode aware and doesn't enforce a specific template
|
||||
engine, database adapter or anything else. It doesn't even enforce
|
||||
a specific way of handling requests and leaves all that up to the
|
||||
developer. It's most useful for end user applications which should work
|
||||
on as many server environments as possible (such as blogs, wikis,
|
||||
bulletin boards, etc.).
|
||||
|
||||
Details and example applications are available on the
|
||||
`Werkzeug website <http://werkzeug.pocoo.org/>`_.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- unicode awareness
|
||||
|
||||
- request and response objects
|
||||
|
||||
- various utility functions for dealing with HTTP headers such as
|
||||
`Accept` and `Cache-Control` headers.
|
||||
|
||||
- thread local objects with proper cleanup at request end
|
||||
|
||||
- an interactive debugger
|
||||
|
||||
- A simple WSGI server with support for threading and forking
|
||||
with an automatic reloader.
|
||||
|
||||
- a flexible URL routing system with REST support.
|
||||
|
||||
- fully WSGI compatible
|
||||
|
||||
|
||||
Development Version
|
||||
-------------------
|
||||
|
||||
The Werkzeug development version can be installed by cloning the git
|
||||
repository from `github`_::
|
||||
|
||||
git clone git@github.com:mitsuhiko/werkzeug.git
|
||||
|
||||
.. _github: http://github.com/mitsuhiko/werkzeug
|
||||
|
||||
|
1
deps/Werkzeug-0.11.11.dist-info/INSTALLER
vendored
Normal file
1
deps/Werkzeug-0.11.11.dist-info/INSTALLER
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
79
deps/Werkzeug-0.11.11.dist-info/METADATA
vendored
Normal file
79
deps/Werkzeug-0.11.11.dist-info/METADATA
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: Werkzeug
|
||||
Version: 0.11.11
|
||||
Summary: The Swiss Army knife of Python web development
|
||||
Home-page: http://werkzeug.pocoo.org/
|
||||
Author: Armin Ronacher
|
||||
Author-email: armin.ronacher@active-4.com
|
||||
License: BSD
|
||||
Platform: any
|
||||
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.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
|
||||
Werkzeug
|
||||
========
|
||||
|
||||
Werkzeug started as simple collection of various utilities for WSGI
|
||||
applications and has become one of the most advanced WSGI utility
|
||||
modules. It includes a powerful debugger, full featured request and
|
||||
response objects, HTTP utilities to handle entity tags, cache control
|
||||
headers, HTTP dates, cookie handling, file uploads, a powerful URL
|
||||
routing system and a bunch of community contributed addon modules.
|
||||
|
||||
Werkzeug is unicode aware and doesn't enforce a specific template
|
||||
engine, database adapter or anything else. It doesn't even enforce
|
||||
a specific way of handling requests and leaves all that up to the
|
||||
developer. It's most useful for end user applications which should work
|
||||
on as many server environments as possible (such as blogs, wikis,
|
||||
bulletin boards, etc.).
|
||||
|
||||
Details and example applications are available on the
|
||||
`Werkzeug website <http://werkzeug.pocoo.org/>`_.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- unicode awareness
|
||||
|
||||
- request and response objects
|
||||
|
||||
- various utility functions for dealing with HTTP headers such as
|
||||
`Accept` and `Cache-Control` headers.
|
||||
|
||||
- thread local objects with proper cleanup at request end
|
||||
|
||||
- an interactive debugger
|
||||
|
||||
- A simple WSGI server with support for threading and forking
|
||||
with an automatic reloader.
|
||||
|
||||
- a flexible URL routing system with REST support.
|
||||
|
||||
- fully WSGI compatible
|
||||
|
||||
|
||||
Development Version
|
||||
-------------------
|
||||
|
||||
The Werkzeug development version can be installed by cloning the git
|
||||
repository from `github`_::
|
||||
|
||||
git clone git@github.com:mitsuhiko/werkzeug.git
|
||||
|
||||
.. _github: http://github.com/mitsuhiko/werkzeug
|
||||
|
||||
|
94
deps/Werkzeug-0.11.11.dist-info/RECORD
vendored
Normal file
94
deps/Werkzeug-0.11.11.dist-info/RECORD
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
werkzeug/posixemulation.py,sha256=xEF2Bxc-vUCPkiu4IbfWVd3LW7DROYAT-ExW6THqyzw,3519
|
||||
werkzeug/security.py,sha256=tuVc22OqoHV5K-TrYJmynCJJa12aUt9BQ3wR_vEPQ34,8971
|
||||
werkzeug/__init__.py,sha256=7sfvCWELBhe774-fzDoL1EU8GHsNG7iuxOkF0OnE5cc,6920
|
||||
werkzeug/testapp.py,sha256=3HQRW1sHZKXuAjCvFMet4KXtQG3loYTFnvn6LWt-4zI,9396
|
||||
werkzeug/http.py,sha256=i9tuTlN8sOy9car-esVsHET6AfNCnObbu1pRBjqExvs,35287
|
||||
werkzeug/routing.py,sha256=TqiZD5HkwdLBnKBUjC5PlytzXmpczQC5dz54VfQzMOw,66350
|
||||
werkzeug/utils.py,sha256=lkybtv_mq35zV1qhelvEcILTzrMUwZ9yon6E8XwapJE,22972
|
||||
werkzeug/exceptions.py,sha256=c-3fKHItsPvC52X_NwBNLcmGXR30h0WP5ynPSwCqPiw,18733
|
||||
werkzeug/_reloader.py,sha256=YQykMSQW7AlojJQ7qOlgNaXw5_CNjf9yzxplwzVdL7Q,8336
|
||||
werkzeug/formparser.py,sha256=90D5Urp8Ghrzw32kAs090G0nXPYlU73NeAzPlQFMVrY,21296
|
||||
werkzeug/_compat.py,sha256=8c4U9o6A_TR9nKCcTbpZNxpqCXcXDVIbFawwKM2s92c,6311
|
||||
werkzeug/datastructures.py,sha256=-W9uZXQ-HSAy_lGDh5oO6cPUE8bNtXoNQ6BaFG8B2Vs,87575
|
||||
werkzeug/wrappers.py,sha256=lKYevpKD1-quk9Cop7bsFxt1eWJxU3h33HCnOI_YzSU,77011
|
||||
werkzeug/test.py,sha256=pQMDJjKdjZVWd_BJfnXExE3NH5Ykr-LG5YT4giptWyw,34127
|
||||
werkzeug/urls.py,sha256=fSbI4Gb29_p02Zk21VAZQRN1QdOVY9CNTgpb2rbajNQ,36710
|
||||
werkzeug/script.py,sha256=DwaVDcXdaOTffdNvlBdLitxWXjKaRVT32VbhDtljFPY,11365
|
||||
werkzeug/useragents.py,sha256=uqpgPcJ5BfcCVh9nPIIl2r3duIrIuENmrbRqbAMmPDk,5418
|
||||
werkzeug/local.py,sha256=4Q5gwHQJhfhZFqTR8iQDs2VHohpR1OEsP4YTwn7rt7w,14275
|
||||
werkzeug/serving.py,sha256=uRUqXuA-Dw2MRA-d232cK_034-taldoj66fEFrtin7k,27736
|
||||
werkzeug/filesystem.py,sha256=hHWeWo_gqLMzTRfYt8-7n2wWcWUNTnDyudQDLOBEICE,2175
|
||||
werkzeug/wsgi.py,sha256=S8R3pBGPlBK67s-d6Wa93nhzG27WjfcHs_ZBGIAQCxM,39573
|
||||
werkzeug/_internal.py,sha256=IEScSoFtQ8KqFH_2ubdfscNAdQ2RIysyVupI5BR9W2U,13709
|
||||
werkzeug/contrib/fixers.py,sha256=MtN_YmENxoTsGvXGGERmtbQ62LaeFc5I2d1YifXNENA,10183
|
||||
werkzeug/contrib/limiter.py,sha256=iS8-ahPZ-JLRnmfIBzxpm7O_s3lPsiDMVWv7llAIDCI,1334
|
||||
werkzeug/contrib/__init__.py,sha256=f7PfttZhbrImqpr5Ezre8CXgwvcGUJK7zWNpO34WWrw,623
|
||||
werkzeug/contrib/testtools.py,sha256=G9xN-qeihJlhExrIZMCahvQOIDxdL9NiX874jiiHFMs,2453
|
||||
werkzeug/contrib/iterio.py,sha256=pTX36rYCKO_9IEoB5sIN5cFSYszI9zdx6YhquWovcPY,10814
|
||||
werkzeug/contrib/cache.py,sha256=4W2WCT9Hw6HEU8yME9GuU4Xf8e50r2K84ASMxhLb6tY,27983
|
||||
werkzeug/contrib/securecookie.py,sha256=X-Ao_0NRDveW6K1Fhe4U42hHWBW8esCpA3VcBDpzWIk,12206
|
||||
werkzeug/contrib/lint.py,sha256=XDKYx0ELn9k18xRn4SiAsCgltCuN4yLjzxnCN8tG_eM,12490
|
||||
werkzeug/contrib/profiler.py,sha256=ISwCWvwVyGpDLRBRpLjo_qUWma6GXYBrTAco4PEQSHY,5151
|
||||
werkzeug/contrib/wrappers.py,sha256=zcd-1yC-kZQOLnn8Bs2SzKUNn7z2H9f0DpcVYS5Ty8s,10380
|
||||
werkzeug/contrib/atom.py,sha256=rvijBrphjMzVObfuCR6ddu6aLwI_SiNiudu64OSTh4Q,15588
|
||||
werkzeug/contrib/jsrouting.py,sha256=QTmgeDoKXvNK02KzXgx9lr3cAH6fAzpwF5bBdPNvJPs,8564
|
||||
werkzeug/contrib/sessions.py,sha256=uAPcnyxaxEla-bUA13gKc3KK4mwSagdzbCZzyKl3PeE,12577
|
||||
werkzeug/debug/console.py,sha256=B7uAu9Rk60siDnGlEt-A_q1ZR4zCtmxx5itg3X-BOxo,5599
|
||||
werkzeug/debug/repr.py,sha256=NaoB89aHb0vuvdSWels-GWdeGDZp76uE4uSNZPX1jAM,9354
|
||||
werkzeug/debug/__init__.py,sha256=qQT5YnOv9Eov9Jt5eLtP6MOqwpmo-tORJ6HcQmmnvro,17271
|
||||
werkzeug/debug/tbtools.py,sha256=-BeFH40ISiF5DFn9RfHMQzCGzmFBovZnREyj-lLzptM,18410
|
||||
werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
|
||||
werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818
|
||||
werkzeug/debug/shared/debugger.js,sha256=PEMBoNuD6fUaNou8Km_ZvVmFcIA3z3k3jSEMWLW-cA0,6187
|
||||
werkzeug/debug/shared/style.css,sha256=7x1s8olZO1XHalqD4M9MWn9vRqQkA635S9_6zRoe220,6231
|
||||
werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
|
||||
werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673
|
||||
werkzeug/debug/shared/jquery.js,sha256=7LkWEzqTdpEfELxcZZlS6wAx5Ff13zZ83lYO2_ujj7g,95957
|
||||
werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220
|
||||
werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
|
||||
Werkzeug-0.11.11.dist-info/DESCRIPTION.rst,sha256=5sTwZ_Sj5aeEN8mlcOdNJ_ng40HiGazGmILLyTMX8o0,1595
|
||||
Werkzeug-0.11.11.dist-info/metadata.json,sha256=aFs0-WetLKLo-qWj4IIgXNtkOaDtX5H6YtZKJuHJVBc,1096
|
||||
Werkzeug-0.11.11.dist-info/RECORD,,
|
||||
Werkzeug-0.11.11.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9
|
||||
Werkzeug-0.11.11.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
|
||||
Werkzeug-0.11.11.dist-info/METADATA,sha256=yIzL70I-ZF_FswTj-dMsH_HvVIlmG8iqrq_lMsqp8WU,2600
|
||||
Werkzeug-0.11.11.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
werkzeug/__pycache__/wsgi.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/wrappers.cpython-34.pyc,,
|
||||
werkzeug/debug/__pycache__/console.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/jsrouting.cpython-34.pyc,,
|
||||
werkzeug/debug/__pycache__/__init__.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/fixers.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/http.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/limiter.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/atom.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/posixemulation.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/__init__.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/formparser.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/useragents.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/iterio.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/serving.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/security.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/local.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/sessions.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/utils.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/lint.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/_reloader.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/datastructures.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/test.cpython-34.pyc,,
|
||||
werkzeug/debug/__pycache__/repr.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/__init__.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/exceptions.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/_compat.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/testtools.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/profiler.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/cache.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/routing.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/_internal.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/filesystem.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/testapp.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/urls.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/securecookie.cpython-34.pyc,,
|
||||
werkzeug/contrib/__pycache__/wrappers.cpython-34.pyc,,
|
||||
werkzeug/debug/__pycache__/tbtools.cpython-34.pyc,,
|
||||
werkzeug/__pycache__/script.cpython-34.pyc,,
|
6
deps/Werkzeug-0.11.11.dist-info/WHEEL
vendored
Normal file
6
deps/Werkzeug-0.11.11.dist-info/WHEEL
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.24.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
1
deps/Werkzeug-0.11.11.dist-info/metadata.json
vendored
Normal file
1
deps/Werkzeug-0.11.11.dist-info/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"license": "BSD", "name": "Werkzeug", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "The Swiss Army knife of Python web development", "platform": "any", "version": "0.11.11", "extensions": {"python.details": {"project_urls": {"Home": "http://werkzeug.pocoo.org/"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "armin.ronacher@active-4.com", "name": "Armin Ronacher"}]}}, "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules"]}
|
1
deps/Werkzeug-0.11.11.dist-info/top_level.txt
vendored
Normal file
1
deps/Werkzeug-0.11.11.dist-info/top_level.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
werkzeug
|
BIN
deps/__pycache__/astral.cpython-34.pyc
vendored
Normal file
BIN
deps/__pycache__/astral.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/__pycache__/phue.cpython-34.pyc
vendored
Normal file
BIN
deps/__pycache__/phue.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/__pycache__/six.cpython-34.pyc
vendored
Normal file
BIN
deps/__pycache__/six.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/__pycache__/speedtest_cli.cpython-34.pyc
vendored
Normal file
BIN
deps/__pycache__/speedtest_cli.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/__pycache__/static.cpython-34.pyc
vendored
Normal file
BIN
deps/__pycache__/static.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/__pycache__/zeroconf.cpython-34.pyc
vendored
Normal file
BIN
deps/__pycache__/zeroconf.cpython-34.pyc
vendored
Normal file
Binary file not shown.
22
deps/astral-1.2.dist-info/DESCRIPTION.rst
vendored
Normal file
22
deps/astral-1.2.dist-info/DESCRIPTION.rst
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
This is 'astral' a Python module which calculates
|
||||
|
||||
* Times for various positions of the sun: dawn, sunrise, solar noon,
|
||||
sunset, dusk, solar elevation, solar azimuth and rahukaalam.
|
||||
* The phase of the moon.
|
||||
|
||||
For documentation see the http://pythonhosted.org/astral/
|
||||
|
||||
GoogleGeocoder
|
||||
--------------
|
||||
|
||||
`GoogleGeocoder` uses the mapping services provided by Google
|
||||
|
||||
Access to the `GoogleGeocoder` requires you to agree to be bound by
|
||||
Google Maps/Google Earth APIs Terms of Service found at
|
||||
https://developers.google.com/maps/terms which includes but is not limited to
|
||||
having a Google Account.
|
||||
|
||||
More information on Google's maps service can be found at
|
||||
https://developers.google.com/maps/documentation/
|
||||
|
||||
|
1
deps/astral-1.2.dist-info/INSTALLER
vendored
Normal file
1
deps/astral-1.2.dist-info/INSTALLER
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
37
deps/astral-1.2.dist-info/METADATA
vendored
Normal file
37
deps/astral-1.2.dist-info/METADATA
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: astral
|
||||
Version: 1.2
|
||||
Summary: Calculations for the position of the sun and moon.
|
||||
Home-page: https://launchpad.net/astral
|
||||
Author: Simon Kennedy
|
||||
Author-email: sffjunkie+code@gmail.com
|
||||
License: Apache-2.0
|
||||
Platform: UNKNOWN
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Requires-Dist: pytz
|
||||
|
||||
This is 'astral' a Python module which calculates
|
||||
|
||||
* Times for various positions of the sun: dawn, sunrise, solar noon,
|
||||
sunset, dusk, solar elevation, solar azimuth and rahukaalam.
|
||||
* The phase of the moon.
|
||||
|
||||
For documentation see the http://pythonhosted.org/astral/
|
||||
|
||||
GoogleGeocoder
|
||||
--------------
|
||||
|
||||
`GoogleGeocoder` uses the mapping services provided by Google
|
||||
|
||||
Access to the `GoogleGeocoder` requires you to agree to be bound by
|
||||
Google Maps/Google Earth APIs Terms of Service found at
|
||||
https://developers.google.com/maps/terms which includes but is not limited to
|
||||
having a Google Account.
|
||||
|
||||
More information on Google's maps service can be found at
|
||||
https://developers.google.com/maps/documentation/
|
||||
|
||||
|
9
deps/astral-1.2.dist-info/RECORD
vendored
Normal file
9
deps/astral-1.2.dist-info/RECORD
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
astral.py,sha256=2H2NtfjQifCApNMEshNuVREdZkmPT4ViYfRLdJR3MH8,90551
|
||||
astral-1.2.dist-info/DESCRIPTION.rst,sha256=LnKJ3t_LHOBmmFcaTJecRsXZZgLgoh1n36C-4OrOkFg,712
|
||||
astral-1.2.dist-info/METADATA,sha256=k7XKsuC2xSZ6m469EpMLcbHdXPkQ_B9RX00uowJzUqk,1165
|
||||
astral-1.2.dist-info/RECORD,,
|
||||
astral-1.2.dist-info/WHEEL,sha256=9Z5Xm-eel1bTS7e6ogYiKz0zmPEqDwIypurdHN1hR40,116
|
||||
astral-1.2.dist-info/metadata.json,sha256=NRjJlS4IUbKVxDj42EqCtqYmOBjBlMYeFs7ehl7Lwyg,698
|
||||
astral-1.2.dist-info/top_level.txt,sha256=XMiS6N151F3-8h-ah-eVWJbJarm3kfI0XlpFa8pET2E,7
|
||||
astral-1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
__pycache__/astral.cpython-34.pyc,,
|
6
deps/astral-1.2.dist-info/WHEEL
vendored
Normal file
6
deps/astral-1.2.dist-info/WHEEL
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
1
deps/astral-1.2.dist-info/metadata.json
vendored
Normal file
1
deps/astral-1.2.dist-info/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"classifiers": ["Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3"], "extensions": {"python.details": {"contacts": [{"email": "sffjunkie+code@gmail.com", "name": "Simon Kennedy", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://launchpad.net/astral"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "license": "Apache-2.0", "metadata_version": "2.0", "name": "astral", "run_requires": [{"requires": ["pytz"]}], "summary": "Calculations for the position of the sun and moon.", "test_requires": [{"requires": ["tox"]}], "version": "1.2"}
|
1
deps/astral-1.2.dist-info/top_level.txt
vendored
Normal file
1
deps/astral-1.2.dist-info/top_level.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
astral
|
2375
deps/astral.py
vendored
Normal file
2375
deps/astral.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
375
deps/cherrypy/__init__.py
vendored
Normal file
375
deps/cherrypy/__init__.py
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
"""CherryPy is a pythonic, object-oriented HTTP framework.
|
||||
|
||||
|
||||
CherryPy consists of not one, but four separate API layers.
|
||||
|
||||
The APPLICATION LAYER is the simplest. CherryPy applications are written as
|
||||
a tree of classes and methods, where each branch in the tree corresponds to
|
||||
a branch in the URL path. Each method is a 'page handler', which receives
|
||||
GET and POST params as keyword arguments, and returns or yields the (HTML)
|
||||
body of the response. The special method name 'index' is used for paths
|
||||
that end in a slash, and the special method name 'default' is used to
|
||||
handle multiple paths via a single handler. This layer also includes:
|
||||
|
||||
* the 'exposed' attribute (and cherrypy.expose)
|
||||
* cherrypy.quickstart()
|
||||
* _cp_config attributes
|
||||
* cherrypy.tools (including cherrypy.session)
|
||||
* cherrypy.url()
|
||||
|
||||
The ENVIRONMENT LAYER is used by developers at all levels. It provides
|
||||
information about the current request and response, plus the application
|
||||
and server environment, via a (default) set of top-level objects:
|
||||
|
||||
* cherrypy.request
|
||||
* cherrypy.response
|
||||
* cherrypy.engine
|
||||
* cherrypy.server
|
||||
* cherrypy.tree
|
||||
* cherrypy.config
|
||||
* cherrypy.thread_data
|
||||
* cherrypy.log
|
||||
* cherrypy.HTTPError, NotFound, and HTTPRedirect
|
||||
* cherrypy.lib
|
||||
|
||||
The EXTENSION LAYER allows advanced users to construct and share their own
|
||||
plugins. It consists of:
|
||||
|
||||
* Hook API
|
||||
* Tool API
|
||||
* Toolbox API
|
||||
* Dispatch API
|
||||
* Config Namespace API
|
||||
|
||||
Finally, there is the CORE LAYER, which uses the core API's to construct
|
||||
the default components which are available at higher layers. You can think
|
||||
of the default components as the 'reference implementation' for CherryPy.
|
||||
Megaframeworks (and advanced users) may replace the default components
|
||||
with customized or extended components. The core API's are:
|
||||
|
||||
* Application API
|
||||
* Engine API
|
||||
* Request API
|
||||
* Server API
|
||||
* WSGI API
|
||||
|
||||
These API's are described in the `CherryPy specification <https://bitbucket.org/cherrypy/cherrypy/wiki/CherryPySpec>`_.
|
||||
"""
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect # noqa
|
||||
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError # noqa
|
||||
|
||||
from cherrypy import _cpdispatch as dispatch # noqa
|
||||
|
||||
from cherrypy import _cptools
|
||||
tools = _cptools.default_toolbox
|
||||
Tool = _cptools.Tool
|
||||
|
||||
from cherrypy import _cprequest
|
||||
from cherrypy.lib import httputil as _httputil
|
||||
|
||||
from cherrypy import _cptree
|
||||
tree = _cptree.Tree()
|
||||
from cherrypy._cptree import Application # noqa
|
||||
from cherrypy import _cpwsgi as wsgi # noqa
|
||||
|
||||
from cherrypy import process
|
||||
try:
|
||||
from cherrypy.process import win32
|
||||
engine = win32.Win32Bus()
|
||||
engine.console_control_handler = win32.ConsoleCtrlHandler(engine)
|
||||
del win32
|
||||
except ImportError:
|
||||
engine = process.bus
|
||||
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.require('cherrypy')[0].version
|
||||
except Exception:
|
||||
__version__ = 'unknown'
|
||||
|
||||
|
||||
# Timeout monitor. We add two channels to the engine
|
||||
# to which cherrypy.Application will publish.
|
||||
engine.listeners['before_request'] = set()
|
||||
engine.listeners['after_request'] = set()
|
||||
|
||||
|
||||
class _TimeoutMonitor(process.plugins.Monitor):
|
||||
|
||||
def __init__(self, bus):
|
||||
self.servings = []
|
||||
process.plugins.Monitor.__init__(self, bus, self.run)
|
||||
|
||||
def before_request(self):
|
||||
self.servings.append((serving.request, serving.response))
|
||||
|
||||
def after_request(self):
|
||||
try:
|
||||
self.servings.remove((serving.request, serving.response))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
"""Check timeout on all responses. (Internal)"""
|
||||
for req, resp in self.servings:
|
||||
resp.check_timeout()
|
||||
engine.timeout_monitor = _TimeoutMonitor(engine)
|
||||
engine.timeout_monitor.subscribe()
|
||||
|
||||
engine.autoreload = process.plugins.Autoreloader(engine)
|
||||
engine.autoreload.subscribe()
|
||||
|
||||
engine.thread_manager = process.plugins.ThreadManager(engine)
|
||||
engine.thread_manager.subscribe()
|
||||
|
||||
engine.signal_handler = process.plugins.SignalHandler(engine)
|
||||
|
||||
|
||||
class _HandleSignalsPlugin(object):
|
||||
|
||||
"""Handle signals from other processes based on the configured
|
||||
platform handlers above."""
|
||||
|
||||
def __init__(self, bus):
|
||||
self.bus = bus
|
||||
|
||||
def subscribe(self):
|
||||
"""Add the handlers based on the platform"""
|
||||
if hasattr(self.bus, "signal_handler"):
|
||||
self.bus.signal_handler.subscribe()
|
||||
if hasattr(self.bus, "console_control_handler"):
|
||||
self.bus.console_control_handler.subscribe()
|
||||
|
||||
engine.signals = _HandleSignalsPlugin(engine)
|
||||
|
||||
|
||||
from cherrypy import _cpserver
|
||||
server = _cpserver.Server()
|
||||
server.subscribe()
|
||||
|
||||
|
||||
def quickstart(root=None, script_name="", config=None):
|
||||
"""Mount the given root, start the builtin server (and engine), then block.
|
||||
|
||||
root: an instance of a "controller class" (a collection of page handler
|
||||
methods) which represents the root of the application.
|
||||
script_name: a string containing the "mount point" of the application.
|
||||
This should start with a slash, and be the path portion of the URL
|
||||
at which to mount the given root. For example, if root.index() will
|
||||
handle requests to "http://www.example.com:8080/dept/app1/", then
|
||||
the script_name argument would be "/dept/app1".
|
||||
|
||||
It MUST NOT end in a slash. If the script_name refers to the root
|
||||
of the URI, it MUST be an empty string (not "/").
|
||||
config: a file or dict containing application config. If this contains
|
||||
a [global] section, those entries will be used in the global
|
||||
(site-wide) config.
|
||||
"""
|
||||
if config:
|
||||
_global_conf_alias.update(config)
|
||||
|
||||
tree.mount(root, script_name, config)
|
||||
|
||||
engine.signals.subscribe()
|
||||
engine.start()
|
||||
engine.block()
|
||||
|
||||
|
||||
from cherrypy._cpcompat import threadlocal as _local
|
||||
|
||||
|
||||
class _Serving(_local):
|
||||
|
||||
"""An interface for registering request and response objects.
|
||||
|
||||
Rather than have a separate "thread local" object for the request and
|
||||
the response, this class works as a single threadlocal container for
|
||||
both objects (and any others which developers wish to define). In this
|
||||
way, we can easily dump those objects when we stop/start a new HTTP
|
||||
conversation, yet still refer to them as module-level globals in a
|
||||
thread-safe way.
|
||||
"""
|
||||
|
||||
request = _cprequest.Request(_httputil.Host("127.0.0.1", 80),
|
||||
_httputil.Host("127.0.0.1", 1111))
|
||||
"""
|
||||
The request object for the current thread. In the main thread,
|
||||
and any threads which are not receiving HTTP requests, this is None."""
|
||||
|
||||
response = _cprequest.Response()
|
||||
"""
|
||||
The response object for the current thread. In the main thread,
|
||||
and any threads which are not receiving HTTP requests, this is None."""
|
||||
|
||||
def load(self, request, response):
|
||||
self.request = request
|
||||
self.response = response
|
||||
|
||||
def clear(self):
|
||||
"""Remove all attributes of self."""
|
||||
self.__dict__.clear()
|
||||
|
||||
serving = _Serving()
|
||||
|
||||
|
||||
class _ThreadLocalProxy(object):
|
||||
|
||||
__slots__ = ['__attrname__', '__dict__']
|
||||
|
||||
def __init__(self, attrname):
|
||||
self.__attrname__ = attrname
|
||||
|
||||
def __getattr__(self, name):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
return getattr(child, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in ("__attrname__", ):
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
child = getattr(serving, self.__attrname__)
|
||||
setattr(child, name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
delattr(child, name)
|
||||
|
||||
def _get_dict(self):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
d = child.__class__.__dict__.copy()
|
||||
d.update(child.__dict__)
|
||||
return d
|
||||
__dict__ = property(_get_dict)
|
||||
|
||||
def __getitem__(self, key):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
return child[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
child[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
del child[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
return key in child
|
||||
|
||||
def __len__(self):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
return len(child)
|
||||
|
||||
def __nonzero__(self):
|
||||
child = getattr(serving, self.__attrname__)
|
||||
return bool(child)
|
||||
# Python 3
|
||||
__bool__ = __nonzero__
|
||||
|
||||
# Create request and response object (the same objects will be used
|
||||
# throughout the entire life of the webserver, but will redirect
|
||||
# to the "serving" object)
|
||||
request = _ThreadLocalProxy('request')
|
||||
response = _ThreadLocalProxy('response')
|
||||
|
||||
# Create thread_data object as a thread-specific all-purpose storage
|
||||
|
||||
|
||||
class _ThreadData(_local):
|
||||
|
||||
"""A container for thread-specific data."""
|
||||
thread_data = _ThreadData()
|
||||
|
||||
|
||||
# Monkeypatch pydoc to allow help() to go through the threadlocal proxy.
|
||||
# Jan 2007: no Googleable examples of anyone else replacing pydoc.resolve.
|
||||
# The only other way would be to change what is returned from type(request)
|
||||
# and that's not possible in pure Python (you'd have to fake ob_type).
|
||||
def _cherrypy_pydoc_resolve(thing, forceload=0):
|
||||
"""Given an object or a path to an object, get the object and its name."""
|
||||
if isinstance(thing, _ThreadLocalProxy):
|
||||
thing = getattr(serving, thing.__attrname__)
|
||||
return _pydoc._builtin_resolve(thing, forceload)
|
||||
|
||||
try:
|
||||
import pydoc as _pydoc
|
||||
_pydoc._builtin_resolve = _pydoc.resolve
|
||||
_pydoc.resolve = _cherrypy_pydoc_resolve
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
from cherrypy import _cplogging
|
||||
|
||||
|
||||
class _GlobalLogManager(_cplogging.LogManager):
|
||||
|
||||
"""A site-wide LogManager; routes to app.log or global log as appropriate.
|
||||
|
||||
This :class:`LogManager<cherrypy._cplogging.LogManager>` implements
|
||||
cherrypy.log() and cherrypy.log.access(). If either
|
||||
function is called during a request, the message will be sent to the
|
||||
logger for the current Application. If they are called outside of a
|
||||
request, the message will be sent to the site-wide logger.
|
||||
"""
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Log the given message to the app.log or global log as appropriate.
|
||||
"""
|
||||
# Do NOT use try/except here. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/945
|
||||
if hasattr(request, 'app') and hasattr(request.app, 'log'):
|
||||
log = request.app.log
|
||||
else:
|
||||
log = self
|
||||
return log.error(*args, **kwargs)
|
||||
|
||||
def access(self):
|
||||
"""Log an access message to the app.log or global log as appropriate.
|
||||
"""
|
||||
try:
|
||||
return request.app.log.access()
|
||||
except AttributeError:
|
||||
return _cplogging.LogManager.access(self)
|
||||
|
||||
|
||||
log = _GlobalLogManager()
|
||||
# Set a default screen handler on the global log.
|
||||
log.screen = True
|
||||
log.error_file = ''
|
||||
# Using an access file makes CP about 10% slower. Leave off by default.
|
||||
log.access_file = ''
|
||||
|
||||
|
||||
def _buslog(msg, level):
|
||||
log.error(msg, 'ENGINE', severity=level)
|
||||
engine.subscribe('log', _buslog)
|
||||
|
||||
from cherrypy._helper import expose, popargs, url # noqa
|
||||
|
||||
# import _cpconfig last so it can reference other top-level objects
|
||||
from cherrypy import _cpconfig
|
||||
# Use _global_conf_alias so quickstart can use 'config' as an arg
|
||||
# without shadowing cherrypy.config.
|
||||
config = _global_conf_alias = _cpconfig.Config()
|
||||
config.defaults = {
|
||||
'tools.log_tracebacks.on': True,
|
||||
'tools.log_headers.on': True,
|
||||
'tools.trailing_slash.on': True,
|
||||
'tools.encode.on': True
|
||||
}
|
||||
config.namespaces["log"] = lambda k, v: setattr(log, k, v)
|
||||
config.namespaces["checker"] = lambda k, v: setattr(checker, k, v)
|
||||
# Must reset to get our defaults applied.
|
||||
config.reset()
|
||||
|
||||
from cherrypy import _cpchecker
|
||||
checker = _cpchecker.Checker()
|
||||
engine.subscribe('start', checker)
|
4
deps/cherrypy/__main__.py
vendored
Normal file
4
deps/cherrypy/__main__.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import cherrypy.daemon
|
||||
|
||||
if __name__ == '__main__':
|
||||
cherrypy.daemon.run()
|
BIN
deps/cherrypy/__pycache__/__init__.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/__init__.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/__main__.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/__main__.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpchecker.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpchecker.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpcompat.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpcompat.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpconfig.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpconfig.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpdispatch.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpdispatch.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cperror.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cperror.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cplogging.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cplogging.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpmodpy.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpmodpy.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpnative_server.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpnative_server.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpreqbody.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpreqbody.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cprequest.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cprequest.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpserver.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpserver.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpthreadinglocal.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpthreadinglocal.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cptools.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cptools.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cptree.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cptree.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpwsgi.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpwsgi.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_cpwsgi_server.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_cpwsgi_server.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/_helper.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/_helper.cpython-34.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/cherrypy/__pycache__/daemon.cpython-34.pyc
vendored
Normal file
BIN
deps/cherrypy/__pycache__/daemon.cpython-34.pyc
vendored
Normal file
Binary file not shown.
332
deps/cherrypy/_cpchecker.py
vendored
Normal file
332
deps/cherrypy/_cpchecker.py
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import iteritems, copykeys, builtins
|
||||
|
||||
|
||||
class Checker(object):
|
||||
|
||||
"""A checker for CherryPy sites and their mounted applications.
|
||||
|
||||
When this object is called at engine startup, it executes each
|
||||
of its own methods whose names start with ``check_``. If you wish
|
||||
to disable selected checks, simply add a line in your global
|
||||
config which sets the appropriate method to False::
|
||||
|
||||
[global]
|
||||
checker.check_skipped_app_config = False
|
||||
|
||||
You may also dynamically add or replace ``check_*`` methods in this way.
|
||||
"""
|
||||
|
||||
on = True
|
||||
"""If True (the default), run all checks; if False, turn off all checks."""
|
||||
|
||||
def __init__(self):
|
||||
self._populate_known_types()
|
||||
|
||||
def __call__(self):
|
||||
"""Run all check_* methods."""
|
||||
if self.on:
|
||||
oldformatwarning = warnings.formatwarning
|
||||
warnings.formatwarning = self.formatwarning
|
||||
try:
|
||||
for name in dir(self):
|
||||
if name.startswith("check_"):
|
||||
method = getattr(self, name)
|
||||
if method and hasattr(method, '__call__'):
|
||||
method()
|
||||
finally:
|
||||
warnings.formatwarning = oldformatwarning
|
||||
|
||||
def formatwarning(self, message, category, filename, lineno, line=None):
|
||||
"""Function to format a warning."""
|
||||
return "CherryPy Checker:\n%s\n\n" % message
|
||||
|
||||
# This value should be set inside _cpconfig.
|
||||
global_config_contained_paths = False
|
||||
|
||||
def check_app_config_entries_dont_start_with_script_name(self):
|
||||
"""Check for Application config with sections that repeat script_name.
|
||||
"""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
if not app.config:
|
||||
continue
|
||||
if sn == '':
|
||||
continue
|
||||
sn_atoms = sn.strip("/").split("/")
|
||||
for key in app.config.keys():
|
||||
key_atoms = key.strip("/").split("/")
|
||||
if key_atoms[:len(sn_atoms)] == sn_atoms:
|
||||
warnings.warn(
|
||||
"The application mounted at %r has config "
|
||||
"entries that start with its script name: %r" % (sn,
|
||||
key))
|
||||
|
||||
def check_site_config_entries_in_app_config(self):
|
||||
"""Check for mounted Applications that have site-scoped config."""
|
||||
for sn, app in iteritems(cherrypy.tree.apps):
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
|
||||
msg = []
|
||||
for section, entries in iteritems(app.config):
|
||||
if section.startswith('/'):
|
||||
for key, value in iteritems(entries):
|
||||
for n in ("engine.", "server.", "tree.", "checker."):
|
||||
if key.startswith(n):
|
||||
msg.append("[%s] %s = %s" %
|
||||
(section, key, value))
|
||||
if msg:
|
||||
msg.insert(0,
|
||||
"The application mounted at %r contains the "
|
||||
"following config entries, which are only allowed "
|
||||
"in site-wide config. Move them to a [global] "
|
||||
"section and pass them to cherrypy.config.update() "
|
||||
"instead of tree.mount()." % sn)
|
||||
warnings.warn(os.linesep.join(msg))
|
||||
|
||||
def check_skipped_app_config(self):
|
||||
"""Check for mounted Applications that have no config."""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
if not app.config:
|
||||
msg = "The Application mounted at %r has an empty config." % sn
|
||||
if self.global_config_contained_paths:
|
||||
msg += (" It looks like the config you passed to "
|
||||
"cherrypy.config.update() contains application-"
|
||||
"specific sections. You must explicitly pass "
|
||||
"application config via "
|
||||
"cherrypy.tree.mount(..., config=app_config)")
|
||||
warnings.warn(msg)
|
||||
return
|
||||
|
||||
def check_app_config_brackets(self):
|
||||
"""Check for Application config with extraneous brackets in section
|
||||
names.
|
||||
"""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
if not app.config:
|
||||
continue
|
||||
for key in app.config.keys():
|
||||
if key.startswith("[") or key.endswith("]"):
|
||||
warnings.warn(
|
||||
"The application mounted at %r has config "
|
||||
"section names with extraneous brackets: %r. "
|
||||
"Config *files* need brackets; config *dicts* "
|
||||
"(e.g. passed to tree.mount) do not." % (sn, key))
|
||||
|
||||
def check_static_paths(self):
|
||||
"""Check Application config for incorrect static paths."""
|
||||
# Use the dummy Request object in the main thread.
|
||||
request = cherrypy.request
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
request.app = app
|
||||
for section in app.config:
|
||||
# get_resource will populate request.config
|
||||
request.get_resource(section + "/dummy.html")
|
||||
conf = request.config.get
|
||||
|
||||
if conf("tools.staticdir.on", False):
|
||||
msg = ""
|
||||
root = conf("tools.staticdir.root")
|
||||
dir = conf("tools.staticdir.dir")
|
||||
if dir is None:
|
||||
msg = "tools.staticdir.dir is not set."
|
||||
else:
|
||||
fulldir = ""
|
||||
if os.path.isabs(dir):
|
||||
fulldir = dir
|
||||
if root:
|
||||
msg = ("dir is an absolute path, even "
|
||||
"though a root is provided.")
|
||||
testdir = os.path.join(root, dir[1:])
|
||||
if os.path.exists(testdir):
|
||||
msg += (
|
||||
"\nIf you meant to serve the "
|
||||
"filesystem folder at %r, remove the "
|
||||
"leading slash from dir." % (testdir,))
|
||||
else:
|
||||
if not root:
|
||||
msg = (
|
||||
"dir is a relative path and "
|
||||
"no root provided.")
|
||||
else:
|
||||
fulldir = os.path.join(root, dir)
|
||||
if not os.path.isabs(fulldir):
|
||||
msg = ("%r is not an absolute path." % (
|
||||
fulldir,))
|
||||
|
||||
if fulldir and not os.path.exists(fulldir):
|
||||
if msg:
|
||||
msg += "\n"
|
||||
msg += ("%r (root + dir) is not an existing "
|
||||
"filesystem path." % fulldir)
|
||||
|
||||
if msg:
|
||||
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
|
||||
% (msg, section, root, dir))
|
||||
|
||||
# -------------------------- Compatibility -------------------------- #
|
||||
obsolete = {
|
||||
'server.default_content_type': 'tools.response_headers.headers',
|
||||
'log_access_file': 'log.access_file',
|
||||
'log_config_options': None,
|
||||
'log_file': 'log.error_file',
|
||||
'log_file_not_found': None,
|
||||
'log_request_headers': 'tools.log_headers.on',
|
||||
'log_to_screen': 'log.screen',
|
||||
'show_tracebacks': 'request.show_tracebacks',
|
||||
'throw_errors': 'request.throw_errors',
|
||||
'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
|
||||
'cherrypy.Application(Root())))'),
|
||||
}
|
||||
|
||||
deprecated = {}
|
||||
|
||||
def _compat(self, config):
|
||||
"""Process config and warn on each obsolete or deprecated entry."""
|
||||
for section, conf in config.items():
|
||||
if isinstance(conf, dict):
|
||||
for k, v in conf.items():
|
||||
if k in self.obsolete:
|
||||
warnings.warn("%r is obsolete. Use %r instead.\n"
|
||||
"section: [%s]" %
|
||||
(k, self.obsolete[k], section))
|
||||
elif k in self.deprecated:
|
||||
warnings.warn("%r is deprecated. Use %r instead.\n"
|
||||
"section: [%s]" %
|
||||
(k, self.deprecated[k], section))
|
||||
else:
|
||||
if section in self.obsolete:
|
||||
warnings.warn("%r is obsolete. Use %r instead."
|
||||
% (section, self.obsolete[section]))
|
||||
elif section in self.deprecated:
|
||||
warnings.warn("%r is deprecated. Use %r instead."
|
||||
% (section, self.deprecated[section]))
|
||||
|
||||
def check_compatibility(self):
|
||||
"""Process config and warn on each obsolete or deprecated entry."""
|
||||
self._compat(cherrypy.config)
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
self._compat(app.config)
|
||||
|
||||
# ------------------------ Known Namespaces ------------------------ #
|
||||
extra_config_namespaces = []
|
||||
|
||||
def _known_ns(self, app):
|
||||
ns = ["wsgi"]
|
||||
ns.extend(copykeys(app.toolboxes))
|
||||
ns.extend(copykeys(app.namespaces))
|
||||
ns.extend(copykeys(app.request_class.namespaces))
|
||||
ns.extend(copykeys(cherrypy.config.namespaces))
|
||||
ns += self.extra_config_namespaces
|
||||
|
||||
for section, conf in app.config.items():
|
||||
is_path_section = section.startswith("/")
|
||||
if is_path_section and isinstance(conf, dict):
|
||||
for k, v in conf.items():
|
||||
atoms = k.split(".")
|
||||
if len(atoms) > 1:
|
||||
if atoms[0] not in ns:
|
||||
# Spit out a special warning if a known
|
||||
# namespace is preceded by "cherrypy."
|
||||
if atoms[0] == "cherrypy" and atoms[1] in ns:
|
||||
msg = (
|
||||
"The config entry %r is invalid; "
|
||||
"try %r instead.\nsection: [%s]"
|
||||
% (k, ".".join(atoms[1:]), section))
|
||||
else:
|
||||
msg = (
|
||||
"The config entry %r is invalid, "
|
||||
"because the %r config namespace "
|
||||
"is unknown.\n"
|
||||
"section: [%s]" % (k, atoms[0], section))
|
||||
warnings.warn(msg)
|
||||
elif atoms[0] == "tools":
|
||||
if atoms[1] not in dir(cherrypy.tools):
|
||||
msg = (
|
||||
"The config entry %r may be invalid, "
|
||||
"because the %r tool was not found.\n"
|
||||
"section: [%s]" % (k, atoms[1], section))
|
||||
warnings.warn(msg)
|
||||
|
||||
def check_config_namespaces(self):
|
||||
"""Process config and warn on each unknown config namespace."""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
self._known_ns(app)
|
||||
|
||||
# -------------------------- Config Types -------------------------- #
|
||||
known_config_types = {}
|
||||
|
||||
def _populate_known_types(self):
|
||||
b = [x for x in vars(builtins).values()
|
||||
if type(x) is type(str)]
|
||||
|
||||
def traverse(obj, namespace):
|
||||
for name in dir(obj):
|
||||
# Hack for 3.2's warning about body_params
|
||||
if name == 'body_params':
|
||||
continue
|
||||
vtype = type(getattr(obj, name, None))
|
||||
if vtype in b:
|
||||
self.known_config_types[namespace + "." + name] = vtype
|
||||
|
||||
traverse(cherrypy.request, "request")
|
||||
traverse(cherrypy.response, "response")
|
||||
traverse(cherrypy.server, "server")
|
||||
traverse(cherrypy.engine, "engine")
|
||||
traverse(cherrypy.log, "log")
|
||||
|
||||
def _known_types(self, config):
|
||||
msg = ("The config entry %r in section %r is of type %r, "
|
||||
"which does not match the expected type %r.")
|
||||
|
||||
for section, conf in config.items():
|
||||
if isinstance(conf, dict):
|
||||
for k, v in conf.items():
|
||||
if v is not None:
|
||||
expected_type = self.known_config_types.get(k, None)
|
||||
vtype = type(v)
|
||||
if expected_type and vtype != expected_type:
|
||||
warnings.warn(msg % (k, section, vtype.__name__,
|
||||
expected_type.__name__))
|
||||
else:
|
||||
k, v = section, conf
|
||||
if v is not None:
|
||||
expected_type = self.known_config_types.get(k, None)
|
||||
vtype = type(v)
|
||||
if expected_type and vtype != expected_type:
|
||||
warnings.warn(msg % (k, section, vtype.__name__,
|
||||
expected_type.__name__))
|
||||
|
||||
def check_config_types(self):
|
||||
"""Assert that config values are of the same type as default values."""
|
||||
self._known_types(cherrypy.config)
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
self._known_types(app.config)
|
||||
|
||||
# -------------------- Specific config warnings -------------------- #
|
||||
def check_localhost(self):
|
||||
"""Warn if any socket_host is 'localhost'. See #711."""
|
||||
for k, v in cherrypy.config.items():
|
||||
if k == 'server.socket_host' and v == 'localhost':
|
||||
warnings.warn("The use of 'localhost' as a socket host can "
|
||||
"cause problems on newer systems, since "
|
||||
"'localhost' can map to either an IPv4 or an "
|
||||
"IPv6 address. You should use '127.0.0.1' "
|
||||
"or '[::1]' instead.")
|
330
deps/cherrypy/_cpcompat.py
vendored
Normal file
330
deps/cherrypy/_cpcompat.py
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
"""Compatibility code for using CherryPy with various versions of Python.
|
||||
|
||||
CherryPy 3.2 is compatible with Python versions 2.6+. This module provides a
|
||||
useful abstraction over the differences between Python versions, sometimes by
|
||||
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
|
||||
|
||||
In particular, Python 2 uses str and '' for byte strings, while Python 3
|
||||
uses str and '' for unicode strings. We will call each of these the 'native
|
||||
string' type for each version. Because of this major difference, this module
|
||||
provides
|
||||
two functions: 'ntob', which translates native strings (of type 'str') into
|
||||
byte strings regardless of Python version, and 'ntou', which translates native
|
||||
strings to unicode strings. This also provides a 'BytesIO' name for dealing
|
||||
specifically with bytes, and a 'StringIO' name for dealing with native strings.
|
||||
It also provides a 'base64_decode' function with native strings as input and
|
||||
output.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
def ntob(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a byte string in the given
|
||||
encoding.
|
||||
"""
|
||||
assert_native(n)
|
||||
# In Python 3, the native string type is unicode
|
||||
return n.encode(encoding)
|
||||
|
||||
def ntou(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a unicode string with the given
|
||||
encoding.
|
||||
"""
|
||||
assert_native(n)
|
||||
# In Python 3, the native string type is unicode
|
||||
return n
|
||||
|
||||
def tonative(n, encoding='ISO-8859-1'):
|
||||
"""Return the given string as a native string in the given encoding."""
|
||||
# In Python 3, the native string type is unicode
|
||||
if isinstance(n, bytes):
|
||||
return n.decode(encoding)
|
||||
return n
|
||||
else:
|
||||
# Python 2
|
||||
def ntob(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a byte string in the given
|
||||
encoding.
|
||||
"""
|
||||
assert_native(n)
|
||||
# In Python 2, the native string type is bytes. Assume it's already
|
||||
# in the given encoding, which for ISO-8859-1 is almost always what
|
||||
# was intended.
|
||||
return n
|
||||
|
||||
def ntou(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a unicode string with the given
|
||||
encoding.
|
||||
"""
|
||||
assert_native(n)
|
||||
# In Python 2, the native string type is bytes.
|
||||
# First, check for the special encoding 'escape'. The test suite uses
|
||||
# this to signal that it wants to pass a string with embedded \uXXXX
|
||||
# escapes, but without having to prefix it with u'' for Python 2,
|
||||
# but no prefix for Python 3.
|
||||
if encoding == 'escape':
|
||||
return unicode(
|
||||
re.sub(r'\\u([0-9a-zA-Z]{4})',
|
||||
lambda m: unichr(int(m.group(1), 16)),
|
||||
n.decode('ISO-8859-1')))
|
||||
# Assume it's already in the given encoding, which for ISO-8859-1
|
||||
# is almost always what was intended.
|
||||
return n.decode(encoding)
|
||||
|
||||
def tonative(n, encoding='ISO-8859-1'):
|
||||
"""Return the given string as a native string in the given encoding."""
|
||||
# In Python 2, the native string type is bytes.
|
||||
if isinstance(n, unicode):
|
||||
return n.encode(encoding)
|
||||
return n
|
||||
|
||||
|
||||
def assert_native(n):
|
||||
if not isinstance(n, str):
|
||||
raise TypeError("n must be a native str (got %s)" % type(n).__name__)
|
||||
|
||||
try:
|
||||
# Python 3.1+
|
||||
from base64 import decodebytes as _base64_decodebytes
|
||||
except ImportError:
|
||||
# Python 3.0-
|
||||
# since CherryPy claims compability with Python 2.3, we must use
|
||||
# the legacy API of base64
|
||||
from base64 import decodestring as _base64_decodebytes
|
||||
|
||||
|
||||
def base64_decode(n, encoding='ISO-8859-1'):
|
||||
"""Return the native string base64-decoded (as a native string)."""
|
||||
if isinstance(n, six.text_type):
|
||||
b = n.encode(encoding)
|
||||
else:
|
||||
b = n
|
||||
b = _base64_decodebytes(b)
|
||||
if str is six.text_type:
|
||||
return b.decode(encoding)
|
||||
else:
|
||||
return b
|
||||
|
||||
|
||||
try:
|
||||
sorted = sorted
|
||||
except NameError:
|
||||
def sorted(i):
|
||||
i = i[:]
|
||||
i.sort()
|
||||
return i
|
||||
|
||||
try:
|
||||
reversed = reversed
|
||||
except NameError:
|
||||
def reversed(x):
|
||||
i = len(x)
|
||||
while i > 0:
|
||||
i -= 1
|
||||
yield x[i]
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from urllib.parse import urljoin, urlencode
|
||||
from urllib.parse import quote, quote_plus
|
||||
from urllib.request import unquote, urlopen
|
||||
from urllib.request import parse_http_list, parse_keqv_list
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from urlparse import urljoin # noqa
|
||||
from urllib import urlencode, urlopen # noqa
|
||||
from urllib import quote, quote_plus # noqa
|
||||
from urllib import unquote # noqa
|
||||
from urllib2 import parse_http_list, parse_keqv_list # noqa
|
||||
|
||||
try:
|
||||
from threading import local as threadlocal
|
||||
except ImportError:
|
||||
from cherrypy._cpthreadinglocal import local as threadlocal # noqa
|
||||
|
||||
try:
|
||||
dict.iteritems
|
||||
# Python 2
|
||||
iteritems = lambda d: d.iteritems()
|
||||
copyitems = lambda d: d.items()
|
||||
except AttributeError:
|
||||
# Python 3
|
||||
iteritems = lambda d: d.items()
|
||||
copyitems = lambda d: list(d.items())
|
||||
|
||||
try:
|
||||
dict.iterkeys
|
||||
# Python 2
|
||||
iterkeys = lambda d: d.iterkeys()
|
||||
copykeys = lambda d: d.keys()
|
||||
except AttributeError:
|
||||
# Python 3
|
||||
iterkeys = lambda d: d.keys()
|
||||
copykeys = lambda d: list(d.keys())
|
||||
|
||||
try:
|
||||
dict.itervalues
|
||||
# Python 2
|
||||
itervalues = lambda d: d.itervalues()
|
||||
copyvalues = lambda d: d.values()
|
||||
except AttributeError:
|
||||
# Python 3
|
||||
itervalues = lambda d: d.values()
|
||||
copyvalues = lambda d: list(d.values())
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
import builtins
|
||||
except ImportError:
|
||||
# Python 2
|
||||
import __builtin__ as builtins # noqa
|
||||
|
||||
try:
|
||||
# Python 2. We try Python 2 first clients on Python 2
|
||||
# don't try to import the 'http' module from cherrypy.lib
|
||||
from Cookie import SimpleCookie, CookieError
|
||||
from httplib import BadStatusLine, HTTPConnection, IncompleteRead
|
||||
from httplib import NotConnected
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
except ImportError:
|
||||
# Python 3
|
||||
from http.cookies import SimpleCookie, CookieError # noqa
|
||||
from http.client import BadStatusLine, HTTPConnection, IncompleteRead # noqa
|
||||
from http.client import NotConnected # noqa
|
||||
from http.server import BaseHTTPRequestHandler # noqa
|
||||
|
||||
# Some platforms don't expose HTTPSConnection, so handle it separately
|
||||
if six.PY3:
|
||||
try:
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
# Some platforms which don't have SSL don't expose HTTPSConnection
|
||||
HTTPSConnection = None
|
||||
else:
|
||||
try:
|
||||
from httplib import HTTPSConnection
|
||||
except ImportError:
|
||||
HTTPSConnection = None
|
||||
|
||||
try:
|
||||
# Python 2
|
||||
xrange = xrange
|
||||
except NameError:
|
||||
# Python 3
|
||||
xrange = range
|
||||
|
||||
import threading
|
||||
if hasattr(threading.Thread, "daemon"):
|
||||
# Python 2.6+
|
||||
def get_daemon(t):
|
||||
return t.daemon
|
||||
|
||||
def set_daemon(t, val):
|
||||
t.daemon = val
|
||||
else:
|
||||
def get_daemon(t):
|
||||
return t.isDaemon()
|
||||
|
||||
def set_daemon(t, val):
|
||||
t.setDaemon(val)
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from urllib.parse import unquote as parse_unquote
|
||||
|
||||
def unquote_qs(atom, encoding, errors='strict'):
|
||||
return parse_unquote(
|
||||
atom.replace('+', ' '),
|
||||
encoding=encoding,
|
||||
errors=errors)
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from urllib import unquote as parse_unquote
|
||||
|
||||
def unquote_qs(atom, encoding, errors='strict'):
|
||||
return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors)
|
||||
|
||||
try:
|
||||
# Prefer simplejson, which is usually more advanced than the builtin
|
||||
# module.
|
||||
import simplejson as json
|
||||
json_decode = json.JSONDecoder().decode
|
||||
_json_encode = json.JSONEncoder().iterencode
|
||||
except ImportError:
|
||||
if sys.version_info >= (2, 6):
|
||||
# Python >=2.6 : json is part of the standard library
|
||||
import json
|
||||
json_decode = json.JSONDecoder().decode
|
||||
_json_encode = json.JSONEncoder().iterencode
|
||||
else:
|
||||
json = None
|
||||
|
||||
def json_decode(s):
|
||||
raise ValueError('No JSON library is available')
|
||||
|
||||
def _json_encode(s):
|
||||
raise ValueError('No JSON library is available')
|
||||
finally:
|
||||
if json and six.PY3:
|
||||
# The two Python 3 implementations (simplejson/json)
|
||||
# outputs str. We need bytes.
|
||||
def json_encode(value):
|
||||
for chunk in _json_encode(value):
|
||||
yield chunk.encode('utf8')
|
||||
else:
|
||||
json_encode = _json_encode
|
||||
|
||||
text_or_bytes = six.text_type, six.binary_type
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
# In Python 2, pickle is a Python version.
|
||||
# In Python 3, pickle is the sped-up C version.
|
||||
import pickle # noqa
|
||||
|
||||
import binascii
|
||||
|
||||
def random20():
|
||||
return binascii.hexlify(os.urandom(20)).decode('ascii')
|
||||
|
||||
try:
|
||||
from _thread import get_ident as get_thread_ident
|
||||
except ImportError:
|
||||
from thread import get_ident as get_thread_ident # noqa
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
next = next
|
||||
except NameError:
|
||||
# Python 2
|
||||
def next(i):
|
||||
return i.next()
|
||||
|
||||
if sys.version_info >= (3, 3):
|
||||
Timer = threading.Timer
|
||||
Event = threading.Event
|
||||
else:
|
||||
# Python 3.2 and earlier
|
||||
Timer = threading._Timer
|
||||
Event = threading._Event
|
||||
|
||||
# Prior to Python 2.6, the Thread class did not have a .daemon property.
|
||||
# This mix-in adds that property.
|
||||
|
||||
|
||||
class SetDaemonProperty:
|
||||
|
||||
def __get_daemon(self):
|
||||
return self.isDaemon()
|
||||
|
||||
def __set_daemon(self, daemon):
|
||||
self.setDaemon(daemon)
|
||||
|
||||
if sys.version_info < (2, 6):
|
||||
daemon = property(__get_daemon, __set_daemon)
|
303
deps/cherrypy/_cpconfig.py
vendored
Normal file
303
deps/cherrypy/_cpconfig.py
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
"""
|
||||
Configuration system for CherryPy.
|
||||
|
||||
Configuration in CherryPy is implemented via dictionaries. Keys are strings
|
||||
which name the mapped value, which may be of any type.
|
||||
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
CherryPy Requests are part of an Application, which runs in a global context,
|
||||
and configuration data may apply to any of those three scopes:
|
||||
|
||||
Global
|
||||
Configuration entries which apply everywhere are stored in
|
||||
cherrypy.config.
|
||||
|
||||
Application
|
||||
Entries which apply to each mounted application are stored
|
||||
on the Application object itself, as 'app.config'. This is a two-level
|
||||
dict where each key is a path, or "relative URL" (for example, "/" or
|
||||
"/path/to/my/page"), and each value is a config dict. Usually, this
|
||||
data is provided in the call to tree.mount(root(), config=conf),
|
||||
although you may also use app.merge(conf).
|
||||
|
||||
Request
|
||||
Each Request object possesses a single 'Request.config' dict.
|
||||
Early in the request process, this dict is populated by merging global
|
||||
config entries, Application entries (whose path equals or is a parent
|
||||
of Request.path_info), and any config acquired while looking up the
|
||||
page handler (see next).
|
||||
|
||||
|
||||
Declaration
|
||||
-----------
|
||||
|
||||
Configuration data may be supplied as a Python dictionary, as a filename,
|
||||
or as an open file object. When you supply a filename or file, CherryPy
|
||||
uses Python's builtin ConfigParser; you declare Application config by
|
||||
writing each path as a section header::
|
||||
|
||||
[/path/to/my/page]
|
||||
request.stream = True
|
||||
|
||||
To declare global configuration entries, place them in a [global] section.
|
||||
|
||||
You may also declare config entries directly on the classes and methods
|
||||
(page handlers) that make up your CherryPy application via the ``_cp_config``
|
||||
attribute, set with the ``cherrypy.config`` decorator. For example::
|
||||
|
||||
@cherrypy.config(**{'tools.gzip.on': True})
|
||||
class Demo:
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.config(**{'request.show_tracebacks': False})
|
||||
def index(self):
|
||||
return "Hello world"
|
||||
|
||||
.. note::
|
||||
|
||||
This behavior is only guaranteed for the default dispatcher.
|
||||
Other dispatchers may have different restrictions on where
|
||||
you can attach config attributes.
|
||||
|
||||
|
||||
Namespaces
|
||||
----------
|
||||
|
||||
Configuration keys are separated into namespaces by the first "." in the key.
|
||||
Current namespaces:
|
||||
|
||||
engine
|
||||
Controls the 'application engine', including autoreload.
|
||||
These can only be declared in the global config.
|
||||
|
||||
tree
|
||||
Grafts cherrypy.Application objects onto cherrypy.tree.
|
||||
These can only be declared in the global config.
|
||||
|
||||
hooks
|
||||
Declares additional request-processing functions.
|
||||
|
||||
log
|
||||
Configures the logging for each application.
|
||||
These can only be declared in the global or / config.
|
||||
|
||||
request
|
||||
Adds attributes to each Request.
|
||||
|
||||
response
|
||||
Adds attributes to each Response.
|
||||
|
||||
server
|
||||
Controls the default HTTP server via cherrypy.server.
|
||||
These can only be declared in the global config.
|
||||
|
||||
tools
|
||||
Runs and configures additional request-processing packages.
|
||||
|
||||
wsgi
|
||||
Adds WSGI middleware to an Application's "pipeline".
|
||||
These can only be declared in the app's root config ("/").
|
||||
|
||||
checker
|
||||
Controls the 'checker', which looks for common errors in
|
||||
app state (including config) when the engine starts.
|
||||
Global config only.
|
||||
|
||||
The only key that does not exist in a namespace is the "environment" entry.
|
||||
This special entry 'imports' other config entries from a template stored in
|
||||
cherrypy._cpconfig.environments[environment]. It only applies to the global
|
||||
config, and only when you use cherrypy.config.update.
|
||||
|
||||
You can define your own namespaces to be called at the Global, Application,
|
||||
or Request level, by adding a named handler to cherrypy.config.namespaces,
|
||||
app.namespaces, or app.request_class.namespaces. The name can
|
||||
be any string, and the handler must be either a callable or a (Python 2.5
|
||||
style) context manager.
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import text_or_bytes
|
||||
from cherrypy.lib import reprconf
|
||||
|
||||
# Deprecated in CherryPy 3.2--remove in 3.3
|
||||
NamespaceSet = reprconf.NamespaceSet
|
||||
|
||||
|
||||
def merge(base, other):
|
||||
"""Merge one app config (from a dict, file, or filename) into another.
|
||||
|
||||
If the given config is a filename, it will be appended to
|
||||
the list of files to monitor for "autoreload" changes.
|
||||
"""
|
||||
if isinstance(other, text_or_bytes):
|
||||
cherrypy.engine.autoreload.files.add(other)
|
||||
|
||||
# Load other into base
|
||||
for section, value_map in reprconf.as_dict(other).items():
|
||||
if not isinstance(value_map, dict):
|
||||
raise ValueError(
|
||||
"Application config must include section headers, but the "
|
||||
"config you tried to merge doesn't have any sections. "
|
||||
"Wrap your config in another dict with paths as section "
|
||||
"headers, for example: {'/': config}.")
|
||||
base.setdefault(section, {}).update(value_map)
|
||||
|
||||
|
||||
class Config(reprconf.Config):
|
||||
|
||||
"""The 'global' configuration data for the entire CherryPy process."""
|
||||
|
||||
def update(self, config):
|
||||
"""Update self from a dict, file or filename."""
|
||||
if isinstance(config, text_or_bytes):
|
||||
# Filename
|
||||
cherrypy.engine.autoreload.files.add(config)
|
||||
reprconf.Config.update(self, config)
|
||||
|
||||
def _apply(self, config):
|
||||
"""Update self from a dict."""
|
||||
if isinstance(config.get("global"), dict):
|
||||
if len(config) > 1:
|
||||
cherrypy.checker.global_config_contained_paths = True
|
||||
config = config["global"]
|
||||
if 'tools.staticdir.dir' in config:
|
||||
config['tools.staticdir.section'] = "global"
|
||||
reprconf.Config._apply(self, config)
|
||||
|
||||
@staticmethod
|
||||
def __call__(*args, **kwargs):
|
||||
"""Decorator for page handlers to set _cp_config."""
|
||||
if args:
|
||||
raise TypeError(
|
||||
"The cherrypy.config decorator does not accept positional "
|
||||
"arguments; you must use keyword arguments.")
|
||||
|
||||
def tool_decorator(f):
|
||||
_Vars(f).setdefault('_cp_config', {}).update(kwargs)
|
||||
return f
|
||||
return tool_decorator
|
||||
|
||||
|
||||
class _Vars(object):
|
||||
"""
|
||||
Adapter that allows setting a default attribute on a function
|
||||
or class.
|
||||
"""
|
||||
def __init__(self, target):
|
||||
self.target = target
|
||||
|
||||
def setdefault(self, key, default):
|
||||
if not hasattr(self.target, key):
|
||||
setattr(self.target, key, default)
|
||||
return getattr(self.target, key)
|
||||
|
||||
|
||||
# Sphinx begin config.environments
|
||||
Config.environments = environments = {
|
||||
"staging": {
|
||||
'engine.autoreload.on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': False,
|
||||
'request.show_mismatched_params': False,
|
||||
},
|
||||
"production": {
|
||||
'engine.autoreload.on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': False,
|
||||
'request.show_mismatched_params': False,
|
||||
'log.screen': False,
|
||||
},
|
||||
"embedded": {
|
||||
# For use with CherryPy embedded in another deployment stack.
|
||||
'engine.autoreload.on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': False,
|
||||
'request.show_mismatched_params': False,
|
||||
'log.screen': False,
|
||||
'engine.SIGHUP': None,
|
||||
'engine.SIGTERM': None,
|
||||
},
|
||||
"test_suite": {
|
||||
'engine.autoreload.on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': True,
|
||||
'request.show_mismatched_params': True,
|
||||
'log.screen': False,
|
||||
},
|
||||
}
|
||||
# Sphinx end config.environments
|
||||
|
||||
|
||||
def _server_namespace_handler(k, v):
|
||||
"""Config handler for the "server" namespace."""
|
||||
atoms = k.split(".", 1)
|
||||
if len(atoms) > 1:
|
||||
# Special-case config keys of the form 'server.servername.socket_port'
|
||||
# to configure additional HTTP servers.
|
||||
if not hasattr(cherrypy, "servers"):
|
||||
cherrypy.servers = {}
|
||||
|
||||
servername, k = atoms
|
||||
if servername not in cherrypy.servers:
|
||||
from cherrypy import _cpserver
|
||||
cherrypy.servers[servername] = _cpserver.Server()
|
||||
# On by default, but 'on = False' can unsubscribe it (see below).
|
||||
cherrypy.servers[servername].subscribe()
|
||||
|
||||
if k == 'on':
|
||||
if v:
|
||||
cherrypy.servers[servername].subscribe()
|
||||
else:
|
||||
cherrypy.servers[servername].unsubscribe()
|
||||
else:
|
||||
setattr(cherrypy.servers[servername], k, v)
|
||||
else:
|
||||
setattr(cherrypy.server, k, v)
|
||||
Config.namespaces["server"] = _server_namespace_handler
|
||||
|
||||
|
||||
def _engine_namespace_handler(k, v):
|
||||
"""Config handler for the "engine" namespace."""
|
||||
engine = cherrypy.engine
|
||||
|
||||
if k == 'SIGHUP':
|
||||
engine.subscribe('SIGHUP', v)
|
||||
elif k == 'SIGTERM':
|
||||
engine.subscribe('SIGTERM', v)
|
||||
elif "." in k:
|
||||
plugin, attrname = k.split(".", 1)
|
||||
plugin = getattr(engine, plugin)
|
||||
if attrname == 'on':
|
||||
if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
|
||||
plugin.subscribe()
|
||||
return
|
||||
elif (
|
||||
(not v) and
|
||||
hasattr(getattr(plugin, 'unsubscribe', None), '__call__')
|
||||
):
|
||||
plugin.unsubscribe()
|
||||
return
|
||||
setattr(plugin, attrname, v)
|
||||
else:
|
||||
setattr(engine, k, v)
|
||||
Config.namespaces["engine"] = _engine_namespace_handler
|
||||
|
||||
|
||||
def _tree_namespace_handler(k, v):
|
||||
"""Namespace handler for the 'tree' config namespace."""
|
||||
if isinstance(v, dict):
|
||||
for script_name, app in v.items():
|
||||
cherrypy.tree.graft(app, script_name)
|
||||
msg = "Mounted: %s on %s" % (app, script_name or "/")
|
||||
cherrypy.engine.log(msg)
|
||||
else:
|
||||
cherrypy.tree.graft(v, v.script_name)
|
||||
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
|
||||
Config.namespaces["tree"] = _tree_namespace_handler
|
685
deps/cherrypy/_cpdispatch.py
vendored
Normal file
685
deps/cherrypy/_cpdispatch.py
vendored
Normal file
@@ -0,0 +1,685 @@
|
||||
"""CherryPy dispatchers.
|
||||
|
||||
A 'dispatcher' is the object which looks up the 'page handler' callable
|
||||
and collects config for the current request based on the path_info, other
|
||||
request attributes, and the application architecture. The core calls the
|
||||
dispatcher as early as possible, passing it a 'path_info' argument.
|
||||
|
||||
The default dispatcher discovers the page handler by matching path_info
|
||||
to a hierarchical arrangement of objects, starting at request.app.root.
|
||||
"""
|
||||
|
||||
import string
|
||||
import sys
|
||||
import types
|
||||
try:
|
||||
classtype = (type, types.ClassType)
|
||||
except AttributeError:
|
||||
classtype = type
|
||||
|
||||
import cherrypy
|
||||
|
||||
|
||||
class PageHandler(object):
|
||||
|
||||
"""Callable which sets response.body."""
|
||||
|
||||
def __init__(self, callable, *args, **kwargs):
|
||||
self.callable = callable
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def get_args(self):
|
||||
return cherrypy.serving.request.args
|
||||
|
||||
def set_args(self, args):
|
||||
cherrypy.serving.request.args = args
|
||||
return cherrypy.serving.request.args
|
||||
|
||||
args = property(
|
||||
get_args,
|
||||
set_args,
|
||||
doc="The ordered args should be accessible from post dispatch hooks"
|
||||
)
|
||||
|
||||
def get_kwargs(self):
|
||||
return cherrypy.serving.request.kwargs
|
||||
|
||||
def set_kwargs(self, kwargs):
|
||||
cherrypy.serving.request.kwargs = kwargs
|
||||
return cherrypy.serving.request.kwargs
|
||||
|
||||
kwargs = property(
|
||||
get_kwargs,
|
||||
set_kwargs,
|
||||
doc="The named kwargs should be accessible from post dispatch hooks"
|
||||
)
|
||||
|
||||
def __call__(self):
|
||||
try:
|
||||
return self.callable(*self.args, **self.kwargs)
|
||||
except TypeError:
|
||||
x = sys.exc_info()[1]
|
||||
try:
|
||||
test_callable_spec(self.callable, self.args, self.kwargs)
|
||||
except cherrypy.HTTPError:
|
||||
raise sys.exc_info()[1]
|
||||
except:
|
||||
raise x
|
||||
raise
|
||||
|
||||
|
||||
def test_callable_spec(callable, callable_args, callable_kwargs):
|
||||
"""
|
||||
Inspect callable and test to see if the given args are suitable for it.
|
||||
|
||||
When an error occurs during the handler's invoking stage there are 2
|
||||
erroneous cases:
|
||||
1. Too many parameters passed to a function which doesn't define
|
||||
one of *args or **kwargs.
|
||||
2. Too little parameters are passed to the function.
|
||||
|
||||
There are 3 sources of parameters to a cherrypy handler.
|
||||
1. query string parameters are passed as keyword parameters to the
|
||||
handler.
|
||||
2. body parameters are also passed as keyword parameters.
|
||||
3. when partial matching occurs, the final path atoms are passed as
|
||||
positional args.
|
||||
Both the query string and path atoms are part of the URI. If they are
|
||||
incorrect, then a 404 Not Found should be raised. Conversely the body
|
||||
parameters are part of the request; if they are invalid a 400 Bad Request.
|
||||
"""
|
||||
show_mismatched_params = getattr(
|
||||
cherrypy.serving.request, 'show_mismatched_params', False)
|
||||
try:
|
||||
(args, varargs, varkw, defaults) = getargspec(callable)
|
||||
except TypeError:
|
||||
if isinstance(callable, object) and hasattr(callable, '__call__'):
|
||||
(args, varargs, varkw,
|
||||
defaults) = getargspec(callable.__call__)
|
||||
else:
|
||||
# If it wasn't one of our own types, re-raise
|
||||
# the original error
|
||||
raise
|
||||
|
||||
if args and args[0] == 'self':
|
||||
args = args[1:]
|
||||
|
||||
arg_usage = dict([(arg, 0,) for arg in args])
|
||||
vararg_usage = 0
|
||||
varkw_usage = 0
|
||||
extra_kwargs = set()
|
||||
|
||||
for i, value in enumerate(callable_args):
|
||||
try:
|
||||
arg_usage[args[i]] += 1
|
||||
except IndexError:
|
||||
vararg_usage += 1
|
||||
|
||||
for key in callable_kwargs.keys():
|
||||
try:
|
||||
arg_usage[key] += 1
|
||||
except KeyError:
|
||||
varkw_usage += 1
|
||||
extra_kwargs.add(key)
|
||||
|
||||
# figure out which args have defaults.
|
||||
args_with_defaults = args[-len(defaults or []):]
|
||||
for i, val in enumerate(defaults or []):
|
||||
# Defaults take effect only when the arg hasn't been used yet.
|
||||
if arg_usage[args_with_defaults[i]] == 0:
|
||||
arg_usage[args_with_defaults[i]] += 1
|
||||
|
||||
missing_args = []
|
||||
multiple_args = []
|
||||
for key, usage in arg_usage.items():
|
||||
if usage == 0:
|
||||
missing_args.append(key)
|
||||
elif usage > 1:
|
||||
multiple_args.append(key)
|
||||
|
||||
if missing_args:
|
||||
# In the case where the method allows body arguments
|
||||
# there are 3 potential errors:
|
||||
# 1. not enough query string parameters -> 404
|
||||
# 2. not enough body parameters -> 400
|
||||
# 3. not enough path parts (partial matches) -> 404
|
||||
#
|
||||
# We can't actually tell which case it is,
|
||||
# so I'm raising a 404 because that covers 2/3 of the
|
||||
# possibilities
|
||||
#
|
||||
# In the case where the method does not allow body
|
||||
# arguments it's definitely a 404.
|
||||
message = None
|
||||
if show_mismatched_params:
|
||||
message = "Missing parameters: %s" % ",".join(missing_args)
|
||||
raise cherrypy.HTTPError(404, message=message)
|
||||
|
||||
# the extra positional arguments come from the path - 404 Not Found
|
||||
if not varargs and vararg_usage > 0:
|
||||
raise cherrypy.HTTPError(404)
|
||||
|
||||
body_params = cherrypy.serving.request.body.params or {}
|
||||
body_params = set(body_params.keys())
|
||||
qs_params = set(callable_kwargs.keys()) - body_params
|
||||
|
||||
if multiple_args:
|
||||
if qs_params.intersection(set(multiple_args)):
|
||||
# If any of the multiple parameters came from the query string then
|
||||
# it's a 404 Not Found
|
||||
error = 404
|
||||
else:
|
||||
# Otherwise it's a 400 Bad Request
|
||||
error = 400
|
||||
|
||||
message = None
|
||||
if show_mismatched_params:
|
||||
message = "Multiple values for parameters: "\
|
||||
"%s" % ",".join(multiple_args)
|
||||
raise cherrypy.HTTPError(error, message=message)
|
||||
|
||||
if not varkw and varkw_usage > 0:
|
||||
|
||||
# If there were extra query string parameters, it's a 404 Not Found
|
||||
extra_qs_params = set(qs_params).intersection(extra_kwargs)
|
||||
if extra_qs_params:
|
||||
message = None
|
||||
if show_mismatched_params:
|
||||
message = "Unexpected query string "\
|
||||
"parameters: %s" % ", ".join(extra_qs_params)
|
||||
raise cherrypy.HTTPError(404, message=message)
|
||||
|
||||
# If there were any extra body parameters, it's a 400 Not Found
|
||||
extra_body_params = set(body_params).intersection(extra_kwargs)
|
||||
if extra_body_params:
|
||||
message = None
|
||||
if show_mismatched_params:
|
||||
message = "Unexpected body parameters: "\
|
||||
"%s" % ", ".join(extra_body_params)
|
||||
raise cherrypy.HTTPError(400, message=message)
|
||||
|
||||
|
||||
try:
|
||||
import inspect
|
||||
except ImportError:
|
||||
test_callable_spec = lambda callable, args, kwargs: None
|
||||
else:
|
||||
getargspec = inspect.getargspec
|
||||
# Python 3 requires using getfullargspec if keyword-only arguments are present
|
||||
if hasattr(inspect, 'getfullargspec'):
|
||||
def getargspec(callable):
|
||||
return inspect.getfullargspec(callable)[:4]
|
||||
|
||||
|
||||
class LateParamPageHandler(PageHandler):
|
||||
|
||||
"""When passing cherrypy.request.params to the page handler, we do not
|
||||
want to capture that dict too early; we want to give tools like the
|
||||
decoding tool a chance to modify the params dict in-between the lookup
|
||||
of the handler and the actual calling of the handler. This subclass
|
||||
takes that into account, and allows request.params to be 'bound late'
|
||||
(it's more complicated than that, but that's the effect).
|
||||
"""
|
||||
|
||||
def _get_kwargs(self):
|
||||
kwargs = cherrypy.serving.request.params.copy()
|
||||
if self._kwargs:
|
||||
kwargs.update(self._kwargs)
|
||||
return kwargs
|
||||
|
||||
def _set_kwargs(self, kwargs):
|
||||
cherrypy.serving.request.kwargs = kwargs
|
||||
self._kwargs = kwargs
|
||||
|
||||
kwargs = property(_get_kwargs, _set_kwargs,
|
||||
doc='page handler kwargs (with '
|
||||
'cherrypy.request.params copied in)')
|
||||
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
punctuation_to_underscores = string.maketrans(
|
||||
string.punctuation, '_' * len(string.punctuation))
|
||||
|
||||
def validate_translator(t):
|
||||
if not isinstance(t, str) or len(t) != 256:
|
||||
raise ValueError(
|
||||
"The translate argument must be a str of len 256.")
|
||||
else:
|
||||
punctuation_to_underscores = str.maketrans(
|
||||
string.punctuation, '_' * len(string.punctuation))
|
||||
|
||||
def validate_translator(t):
|
||||
if not isinstance(t, dict):
|
||||
raise ValueError("The translate argument must be a dict.")
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
|
||||
"""CherryPy Dispatcher which walks a tree of objects to find a handler.
|
||||
|
||||
The tree is rooted at cherrypy.request.app.root, and each hierarchical
|
||||
component in the path_info argument is matched to a corresponding nested
|
||||
attribute of the root object. Matching handlers must have an 'exposed'
|
||||
attribute which evaluates to True. The special method name "index"
|
||||
matches a URI which ends in a slash ("/"). The special method name
|
||||
"default" may match a portion of the path_info (but only when no longer
|
||||
substring of the path_info matches some other object).
|
||||
|
||||
This is the default, built-in dispatcher for CherryPy.
|
||||
"""
|
||||
|
||||
dispatch_method_name = '_cp_dispatch'
|
||||
"""
|
||||
The name of the dispatch method that nodes may optionally implement
|
||||
to provide their own dynamic dispatch algorithm.
|
||||
"""
|
||||
|
||||
def __init__(self, dispatch_method_name=None,
|
||||
translate=punctuation_to_underscores):
|
||||
validate_translator(translate)
|
||||
self.translate = translate
|
||||
if dispatch_method_name:
|
||||
self.dispatch_method_name = dispatch_method_name
|
||||
|
||||
def __call__(self, path_info):
|
||||
"""Set handler and config for the current request."""
|
||||
request = cherrypy.serving.request
|
||||
func, vpath = self.find_handler(path_info)
|
||||
|
||||
if func:
|
||||
# Decode any leftover %2F in the virtual_path atoms.
|
||||
vpath = [x.replace("%2F", "/") for x in vpath]
|
||||
request.handler = LateParamPageHandler(func, *vpath)
|
||||
else:
|
||||
request.handler = cherrypy.NotFound()
|
||||
|
||||
def find_handler(self, path):
|
||||
"""Return the appropriate page handler, plus any virtual path.
|
||||
|
||||
This will return two objects. The first will be a callable,
|
||||
which can be used to generate page output. Any parameters from
|
||||
the query string or request body will be sent to that callable
|
||||
as keyword arguments.
|
||||
|
||||
The callable is found by traversing the application's tree,
|
||||
starting from cherrypy.request.app.root, and matching path
|
||||
components to successive objects in the tree. For example, the
|
||||
URL "/path/to/handler" might return root.path.to.handler.
|
||||
|
||||
The second object returned will be a list of names which are
|
||||
'virtual path' components: parts of the URL which are dynamic,
|
||||
and were not used when looking up the handler.
|
||||
These virtual path components are passed to the handler as
|
||||
positional arguments.
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
app = request.app
|
||||
root = app.root
|
||||
dispatch_name = self.dispatch_method_name
|
||||
|
||||
# Get config for the root object/path.
|
||||
fullpath = [x for x in path.strip('/').split('/') if x] + ['index']
|
||||
fullpath_len = len(fullpath)
|
||||
segleft = fullpath_len
|
||||
nodeconf = {}
|
||||
if hasattr(root, "_cp_config"):
|
||||
nodeconf.update(root._cp_config)
|
||||
if "/" in app.config:
|
||||
nodeconf.update(app.config["/"])
|
||||
object_trail = [['root', root, nodeconf, segleft]]
|
||||
|
||||
node = root
|
||||
iternames = fullpath[:]
|
||||
while iternames:
|
||||
name = iternames[0]
|
||||
# map to legal Python identifiers (e.g. replace '.' with '_')
|
||||
objname = name.translate(self.translate)
|
||||
|
||||
nodeconf = {}
|
||||
subnode = getattr(node, objname, None)
|
||||
pre_len = len(iternames)
|
||||
if subnode is None:
|
||||
dispatch = getattr(node, dispatch_name, None)
|
||||
if dispatch and hasattr(dispatch, '__call__') and not \
|
||||
getattr(dispatch, 'exposed', False) and \
|
||||
pre_len > 1:
|
||||
# Don't expose the hidden 'index' token to _cp_dispatch
|
||||
# We skip this if pre_len == 1 since it makes no sense
|
||||
# to call a dispatcher when we have no tokens left.
|
||||
index_name = iternames.pop()
|
||||
subnode = dispatch(vpath=iternames)
|
||||
iternames.append(index_name)
|
||||
else:
|
||||
# We didn't find a path, but keep processing in case there
|
||||
# is a default() handler.
|
||||
iternames.pop(0)
|
||||
else:
|
||||
# We found the path, remove the vpath entry
|
||||
iternames.pop(0)
|
||||
segleft = len(iternames)
|
||||
if segleft > pre_len:
|
||||
# No path segment was removed. Raise an error.
|
||||
raise cherrypy.CherryPyException(
|
||||
"A vpath segment was added. Custom dispatchers may only "
|
||||
+ "remove elements. While trying to process "
|
||||
+ "{0} in {1}".format(name, fullpath)
|
||||
)
|
||||
elif segleft == pre_len:
|
||||
# Assume that the handler used the current path segment, but
|
||||
# did not pop it. This allows things like
|
||||
# return getattr(self, vpath[0], None)
|
||||
iternames.pop(0)
|
||||
segleft -= 1
|
||||
node = subnode
|
||||
|
||||
if node is not None:
|
||||
# Get _cp_config attached to this node.
|
||||
if hasattr(node, "_cp_config"):
|
||||
nodeconf.update(node._cp_config)
|
||||
|
||||
# Mix in values from app.config for this path.
|
||||
existing_len = fullpath_len - pre_len
|
||||
if existing_len != 0:
|
||||
curpath = '/' + '/'.join(fullpath[0:existing_len])
|
||||
else:
|
||||
curpath = ''
|
||||
new_segs = fullpath[fullpath_len - pre_len:fullpath_len - segleft]
|
||||
for seg in new_segs:
|
||||
curpath += '/' + seg
|
||||
if curpath in app.config:
|
||||
nodeconf.update(app.config[curpath])
|
||||
|
||||
object_trail.append([name, node, nodeconf, segleft])
|
||||
|
||||
def set_conf():
|
||||
"""Collapse all object_trail config into cherrypy.request.config.
|
||||
"""
|
||||
base = cherrypy.config.copy()
|
||||
# Note that we merge the config from each node
|
||||
# even if that node was None.
|
||||
for name, obj, conf, segleft in object_trail:
|
||||
base.update(conf)
|
||||
if 'tools.staticdir.dir' in conf:
|
||||
base['tools.staticdir.section'] = '/' + \
|
||||
'/'.join(fullpath[0:fullpath_len - segleft])
|
||||
return base
|
||||
|
||||
# Try successive objects (reverse order)
|
||||
num_candidates = len(object_trail) - 1
|
||||
for i in range(num_candidates, -1, -1):
|
||||
|
||||
name, candidate, nodeconf, segleft = object_trail[i]
|
||||
if candidate is None:
|
||||
continue
|
||||
|
||||
# Try a "default" method on the current leaf.
|
||||
if hasattr(candidate, "default"):
|
||||
defhandler = candidate.default
|
||||
if getattr(defhandler, 'exposed', False):
|
||||
# Insert any extra _cp_config from the default handler.
|
||||
conf = getattr(defhandler, "_cp_config", {})
|
||||
object_trail.insert(
|
||||
i + 1, ["default", defhandler, conf, segleft])
|
||||
request.config = set_conf()
|
||||
# See https://github.com/cherrypy/cherrypy/issues/613
|
||||
request.is_index = path.endswith("/")
|
||||
return defhandler, fullpath[fullpath_len - segleft:-1]
|
||||
|
||||
# Uncomment the next line to restrict positional params to
|
||||
# "default".
|
||||
# if i < num_candidates - 2: continue
|
||||
|
||||
# Try the current leaf.
|
||||
if getattr(candidate, 'exposed', False):
|
||||
request.config = set_conf()
|
||||
if i == num_candidates:
|
||||
# We found the extra ".index". Mark request so tools
|
||||
# can redirect if path_info has no trailing slash.
|
||||
request.is_index = True
|
||||
else:
|
||||
# We're not at an 'index' handler. Mark request so tools
|
||||
# can redirect if path_info has NO trailing slash.
|
||||
# Note that this also includes handlers which take
|
||||
# positional parameters (virtual paths).
|
||||
request.is_index = False
|
||||
return candidate, fullpath[fullpath_len - segleft:-1]
|
||||
|
||||
# We didn't find anything
|
||||
request.config = set_conf()
|
||||
return None, []
|
||||
|
||||
|
||||
class MethodDispatcher(Dispatcher):
|
||||
|
||||
"""Additional dispatch based on cherrypy.request.method.upper().
|
||||
|
||||
Methods named GET, POST, etc will be called on an exposed class.
|
||||
The method names must be all caps; the appropriate Allow header
|
||||
will be output showing all capitalized method names as allowable
|
||||
HTTP verbs.
|
||||
|
||||
Note that the containing class must be exposed, not the methods.
|
||||
"""
|
||||
|
||||
def __call__(self, path_info):
|
||||
"""Set handler and config for the current request."""
|
||||
request = cherrypy.serving.request
|
||||
resource, vpath = self.find_handler(path_info)
|
||||
|
||||
if resource:
|
||||
# Set Allow header
|
||||
avail = [m for m in dir(resource) if m.isupper()]
|
||||
if "GET" in avail and "HEAD" not in avail:
|
||||
avail.append("HEAD")
|
||||
avail.sort()
|
||||
cherrypy.serving.response.headers['Allow'] = ", ".join(avail)
|
||||
|
||||
# Find the subhandler
|
||||
meth = request.method.upper()
|
||||
func = getattr(resource, meth, None)
|
||||
if func is None and meth == "HEAD":
|
||||
func = getattr(resource, "GET", None)
|
||||
if func:
|
||||
# Grab any _cp_config on the subhandler.
|
||||
if hasattr(func, "_cp_config"):
|
||||
request.config.update(func._cp_config)
|
||||
|
||||
# Decode any leftover %2F in the virtual_path atoms.
|
||||
vpath = [x.replace("%2F", "/") for x in vpath]
|
||||
request.handler = LateParamPageHandler(func, *vpath)
|
||||
else:
|
||||
request.handler = cherrypy.HTTPError(405)
|
||||
else:
|
||||
request.handler = cherrypy.NotFound()
|
||||
|
||||
|
||||
class RoutesDispatcher(object):
|
||||
|
||||
"""A Routes based dispatcher for CherryPy."""
|
||||
|
||||
def __init__(self, full_result=False, **mapper_options):
|
||||
"""
|
||||
Routes dispatcher
|
||||
|
||||
Set full_result to True if you wish the controller
|
||||
and the action to be passed on to the page handler
|
||||
parameters. By default they won't be.
|
||||
"""
|
||||
import routes
|
||||
self.full_result = full_result
|
||||
self.controllers = {}
|
||||
self.mapper = routes.Mapper(**mapper_options)
|
||||
self.mapper.controller_scan = self.controllers.keys
|
||||
|
||||
def connect(self, name, route, controller, **kwargs):
|
||||
self.controllers[name] = controller
|
||||
self.mapper.connect(name, route, controller=name, **kwargs)
|
||||
|
||||
def redirect(self, url):
|
||||
raise cherrypy.HTTPRedirect(url)
|
||||
|
||||
def __call__(self, path_info):
|
||||
"""Set handler and config for the current request."""
|
||||
func = self.find_handler(path_info)
|
||||
if func:
|
||||
cherrypy.serving.request.handler = LateParamPageHandler(func)
|
||||
else:
|
||||
cherrypy.serving.request.handler = cherrypy.NotFound()
|
||||
|
||||
def find_handler(self, path_info):
|
||||
"""Find the right page handler, and set request.config."""
|
||||
import routes
|
||||
|
||||
request = cherrypy.serving.request
|
||||
|
||||
config = routes.request_config()
|
||||
config.mapper = self.mapper
|
||||
if hasattr(request, 'wsgi_environ'):
|
||||
config.environ = request.wsgi_environ
|
||||
config.host = request.headers.get('Host', None)
|
||||
config.protocol = request.scheme
|
||||
config.redirect = self.redirect
|
||||
|
||||
result = self.mapper.match(path_info)
|
||||
|
||||
config.mapper_dict = result
|
||||
params = {}
|
||||
if result:
|
||||
params = result.copy()
|
||||
if not self.full_result:
|
||||
params.pop('controller', None)
|
||||
params.pop('action', None)
|
||||
request.params.update(params)
|
||||
|
||||
# Get config for the root object/path.
|
||||
request.config = base = cherrypy.config.copy()
|
||||
curpath = ""
|
||||
|
||||
def merge(nodeconf):
|
||||
if 'tools.staticdir.dir' in nodeconf:
|
||||
nodeconf['tools.staticdir.section'] = curpath or "/"
|
||||
base.update(nodeconf)
|
||||
|
||||
app = request.app
|
||||
root = app.root
|
||||
if hasattr(root, "_cp_config"):
|
||||
merge(root._cp_config)
|
||||
if "/" in app.config:
|
||||
merge(app.config["/"])
|
||||
|
||||
# Mix in values from app.config.
|
||||
atoms = [x for x in path_info.split("/") if x]
|
||||
if atoms:
|
||||
last = atoms.pop()
|
||||
else:
|
||||
last = None
|
||||
for atom in atoms:
|
||||
curpath = "/".join((curpath, atom))
|
||||
if curpath in app.config:
|
||||
merge(app.config[curpath])
|
||||
|
||||
handler = None
|
||||
if result:
|
||||
controller = result.get('controller')
|
||||
controller = self.controllers.get(controller, controller)
|
||||
if controller:
|
||||
if isinstance(controller, classtype):
|
||||
controller = controller()
|
||||
# Get config from the controller.
|
||||
if hasattr(controller, "_cp_config"):
|
||||
merge(controller._cp_config)
|
||||
|
||||
action = result.get('action')
|
||||
if action is not None:
|
||||
handler = getattr(controller, action, None)
|
||||
# Get config from the handler
|
||||
if hasattr(handler, "_cp_config"):
|
||||
merge(handler._cp_config)
|
||||
else:
|
||||
handler = controller
|
||||
|
||||
# Do the last path atom here so it can
|
||||
# override the controller's _cp_config.
|
||||
if last:
|
||||
curpath = "/".join((curpath, last))
|
||||
if curpath in app.config:
|
||||
merge(app.config[curpath])
|
||||
|
||||
return handler
|
||||
|
||||
|
||||
def XMLRPCDispatcher(next_dispatcher=Dispatcher()):
|
||||
from cherrypy.lib import xmlrpcutil
|
||||
|
||||
def xmlrpc_dispatch(path_info):
|
||||
path_info = xmlrpcutil.patched_path(path_info)
|
||||
return next_dispatcher(path_info)
|
||||
return xmlrpc_dispatch
|
||||
|
||||
|
||||
def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True,
|
||||
**domains):
|
||||
"""
|
||||
Select a different handler based on the Host header.
|
||||
|
||||
This can be useful when running multiple sites within one CP server.
|
||||
It allows several domains to point to different parts of a single
|
||||
website structure. For example::
|
||||
|
||||
http://www.domain.example -> root
|
||||
http://www.domain2.example -> root/domain2/
|
||||
http://www.domain2.example:443 -> root/secure
|
||||
|
||||
can be accomplished via the following config::
|
||||
|
||||
[/]
|
||||
request.dispatch = cherrypy.dispatch.VirtualHost(
|
||||
**{'www.domain2.example': '/domain2',
|
||||
'www.domain2.example:443': '/secure',
|
||||
})
|
||||
|
||||
next_dispatcher
|
||||
The next dispatcher object in the dispatch chain.
|
||||
The VirtualHost dispatcher adds a prefix to the URL and calls
|
||||
another dispatcher. Defaults to cherrypy.dispatch.Dispatcher().
|
||||
|
||||
use_x_forwarded_host
|
||||
If True (the default), any "X-Forwarded-Host"
|
||||
request header will be used instead of the "Host" header. This
|
||||
is commonly added by HTTP servers (such as Apache) when proxying.
|
||||
|
||||
``**domains``
|
||||
A dict of {host header value: virtual prefix} pairs.
|
||||
The incoming "Host" request header is looked up in this dict,
|
||||
and, if a match is found, the corresponding "virtual prefix"
|
||||
value will be prepended to the URL path before calling the
|
||||
next dispatcher. Note that you often need separate entries
|
||||
for "example.com" and "www.example.com". In addition, "Host"
|
||||
headers may contain the port number.
|
||||
"""
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
def vhost_dispatch(path_info):
|
||||
request = cherrypy.serving.request
|
||||
header = request.headers.get
|
||||
|
||||
domain = header('Host', '')
|
||||
if use_x_forwarded_host:
|
||||
domain = header("X-Forwarded-Host", domain)
|
||||
|
||||
prefix = domains.get(domain, "")
|
||||
if prefix:
|
||||
path_info = httputil.urljoin(prefix, path_info)
|
||||
|
||||
result = next_dispatcher(path_info)
|
||||
|
||||
# Touch up staticdir config. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/614.
|
||||
section = request.config.get('tools.staticdir.section')
|
||||
if section:
|
||||
section = section[len(prefix):]
|
||||
request.config['tools.staticdir.section'] = section
|
||||
|
||||
return result
|
||||
return vhost_dispatch
|
622
deps/cherrypy/_cperror.py
vendored
Normal file
622
deps/cherrypy/_cperror.py
vendored
Normal file
@@ -0,0 +1,622 @@
|
||||
"""Exception classes for CherryPy.
|
||||
|
||||
CherryPy provides (and uses) exceptions for declaring that the HTTP response
|
||||
should be a status other than the default "200 OK". You can ``raise`` them like
|
||||
normal Python exceptions. You can also call them and they will raise
|
||||
themselves; this means you can set an
|
||||
:class:`HTTPError<cherrypy._cperror.HTTPError>`
|
||||
or :class:`HTTPRedirect<cherrypy._cperror.HTTPRedirect>` as the
|
||||
:attr:`request.handler<cherrypy._cprequest.Request.handler>`.
|
||||
|
||||
.. _redirectingpost:
|
||||
|
||||
Redirecting POST
|
||||
================
|
||||
|
||||
When you GET a resource and are redirected by the server to another Location,
|
||||
there's generally no problem since GET is both a "safe method" (there should
|
||||
be no side-effects) and an "idempotent method" (multiple calls are no different
|
||||
than a single call).
|
||||
|
||||
POST, however, is neither safe nor idempotent--if you
|
||||
charge a credit card, you don't want to be charged twice by a redirect!
|
||||
|
||||
For this reason, *none* of the 3xx responses permit a user-agent (browser) to
|
||||
resubmit a POST on redirection without first confirming the action with the
|
||||
user:
|
||||
|
||||
===== ================================= ===========
|
||||
300 Multiple Choices Confirm with the user
|
||||
301 Moved Permanently Confirm with the user
|
||||
302 Found (Object moved temporarily) Confirm with the user
|
||||
303 See Other GET the new URI--no confirmation
|
||||
304 Not modified (for conditional GET only--POST should not raise this error)
|
||||
305 Use Proxy Confirm with the user
|
||||
307 Temporary Redirect Confirm with the user
|
||||
===== ================================= ===========
|
||||
|
||||
However, browsers have historically implemented these restrictions poorly;
|
||||
in particular, many browsers do not force the user to confirm 301, 302
|
||||
or 307 when redirecting POST. For this reason, CherryPy defaults to 303,
|
||||
which most user-agents appear to have implemented correctly. Therefore, if
|
||||
you raise HTTPRedirect for a POST request, the user-agent will most likely
|
||||
attempt to GET the new URI (without asking for confirmation from the user).
|
||||
We realize this is confusing for developers, but it's the safest thing we
|
||||
could do. You are of course free to raise ``HTTPRedirect(uri, status=302)``
|
||||
or any other 3xx status if you know what you're doing, but given the
|
||||
environment, we couldn't let any of those be the default.
|
||||
|
||||
Custom Error Handling
|
||||
=====================
|
||||
|
||||
.. image:: /refman/cperrors.gif
|
||||
|
||||
Anticipated HTTP responses
|
||||
--------------------------
|
||||
|
||||
The 'error_page' config namespace can be used to provide custom HTML output for
|
||||
expected responses (like 404 Not Found). Supply a filename from which the
|
||||
output will be read. The contents will be interpolated with the values
|
||||
%(status)s, %(message)s, %(traceback)s, and %(version)s using plain old Python
|
||||
`string formatting <http://docs.python.org/2/library/stdtypes.html#string-formatting-operations>`_.
|
||||
|
||||
::
|
||||
|
||||
_cp_config = {
|
||||
'error_page.404': os.path.join(localDir, "static/index.html")
|
||||
}
|
||||
|
||||
|
||||
Beginning in version 3.1, you may also provide a function or other callable as
|
||||
an error_page entry. It will be passed the same status, message, traceback and
|
||||
version arguments that are interpolated into templates::
|
||||
|
||||
def error_page_402(status, message, traceback, version):
|
||||
return "Error %s - Well, I'm very sorry but you haven't paid!" % status
|
||||
cherrypy.config.update({'error_page.402': error_page_402})
|
||||
|
||||
Also in 3.1, in addition to the numbered error codes, you may also supply
|
||||
"error_page.default" to handle all codes which do not have their own error_page
|
||||
entry.
|
||||
|
||||
|
||||
|
||||
Unanticipated errors
|
||||
--------------------
|
||||
|
||||
CherryPy also has a generic error handling mechanism: whenever an unanticipated
|
||||
error occurs in your code, it will call
|
||||
:func:`Request.error_response<cherrypy._cprequest.Request.error_response>` to
|
||||
set the response status, headers, and body. By default, this is the same
|
||||
output as
|
||||
:class:`HTTPError(500) <cherrypy._cperror.HTTPError>`. If you want to provide
|
||||
some other behavior, you generally replace "request.error_response".
|
||||
|
||||
Here is some sample code that shows how to display a custom error message and
|
||||
send an e-mail containing the error::
|
||||
|
||||
from cherrypy import _cperror
|
||||
|
||||
def handle_error():
|
||||
cherrypy.response.status = 500
|
||||
cherrypy.response.body = [
|
||||
"<html><body>Sorry, an error occured</body></html>"
|
||||
]
|
||||
sendMail('error@domain.com',
|
||||
'Error in your web app',
|
||||
_cperror.format_exc())
|
||||
|
||||
@cherrypy.config(**{'request.error_response': handle_error})
|
||||
class Root:
|
||||
pass
|
||||
|
||||
Note that you have to explicitly set
|
||||
:attr:`response.body <cherrypy._cprequest.Response.body>`
|
||||
and not simply return an error message as a result.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
from cgi import escape as _escape
|
||||
from sys import exc_info as _exc_info
|
||||
from traceback import format_exception as _format_exception
|
||||
from xml.sax import saxutils
|
||||
|
||||
import six
|
||||
|
||||
from cherrypy._cpcompat import text_or_bytes, iteritems, ntob
|
||||
from cherrypy._cpcompat import tonative, urljoin as _urljoin
|
||||
from cherrypy.lib import httputil as _httputil
|
||||
|
||||
|
||||
class CherryPyException(Exception):
|
||||
|
||||
"""A base class for CherryPy exceptions."""
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutError(CherryPyException):
|
||||
|
||||
"""Exception raised when Response.timed_out is detected."""
|
||||
pass
|
||||
|
||||
|
||||
class InternalRedirect(CherryPyException):
|
||||
|
||||
"""Exception raised to switch to the handler for a different URL.
|
||||
|
||||
This exception will redirect processing to another path within the site
|
||||
(without informing the client). Provide the new path as an argument when
|
||||
raising the exception. Provide any params in the querystring for the new
|
||||
URL.
|
||||
"""
|
||||
|
||||
def __init__(self, path, query_string=""):
|
||||
import cherrypy
|
||||
self.request = cherrypy.serving.request
|
||||
|
||||
self.query_string = query_string
|
||||
if "?" in path:
|
||||
# Separate any params included in the path
|
||||
path, self.query_string = path.split("?", 1)
|
||||
|
||||
# Note that urljoin will "do the right thing" whether url is:
|
||||
# 1. a URL relative to root (e.g. "/dummy")
|
||||
# 2. a URL relative to the current path
|
||||
# Note that any query string will be discarded.
|
||||
path = _urljoin(self.request.path_info, path)
|
||||
|
||||
# Set a 'path' member attribute so that code which traps this
|
||||
# error can have access to it.
|
||||
self.path = path
|
||||
|
||||
CherryPyException.__init__(self, path, self.query_string)
|
||||
|
||||
|
||||
class HTTPRedirect(CherryPyException):
|
||||
|
||||
"""Exception raised when the request should be redirected.
|
||||
|
||||
This exception will force a HTTP redirect to the URL or URL's you give it.
|
||||
The new URL must be passed as the first argument to the Exception,
|
||||
e.g., HTTPRedirect(newUrl). Multiple URLs are allowed in a list.
|
||||
If a URL is absolute, it will be used as-is. If it is relative, it is
|
||||
assumed to be relative to the current cherrypy.request.path_info.
|
||||
|
||||
If one of the provided URL is a unicode object, it will be encoded
|
||||
using the default encoding or the one passed in parameter.
|
||||
|
||||
There are multiple types of redirect, from which you can select via the
|
||||
``status`` argument. If you do not provide a ``status`` arg, it defaults to
|
||||
303 (or 302 if responding with HTTP/1.0).
|
||||
|
||||
Examples::
|
||||
|
||||
raise cherrypy.HTTPRedirect("")
|
||||
raise cherrypy.HTTPRedirect("/abs/path", 307)
|
||||
raise cherrypy.HTTPRedirect(["path1", "path2?a=1&b=2"], 301)
|
||||
|
||||
See :ref:`redirectingpost` for additional caveats.
|
||||
"""
|
||||
|
||||
status = None
|
||||
"""The integer HTTP status code to emit."""
|
||||
|
||||
urls = None
|
||||
"""The list of URL's to emit."""
|
||||
|
||||
encoding = 'utf-8'
|
||||
"""The encoding when passed urls are not native strings"""
|
||||
|
||||
def __init__(self, urls, status=None, encoding=None):
|
||||
import cherrypy
|
||||
request = cherrypy.serving.request
|
||||
|
||||
if isinstance(urls, text_or_bytes):
|
||||
urls = [urls]
|
||||
|
||||
abs_urls = []
|
||||
for url in urls:
|
||||
url = tonative(url, encoding or self.encoding)
|
||||
|
||||
# Note that urljoin will "do the right thing" whether url is:
|
||||
# 1. a complete URL with host (e.g. "http://www.example.com/test")
|
||||
# 2. a URL relative to root (e.g. "/dummy")
|
||||
# 3. a URL relative to the current path
|
||||
# Note that any query string in cherrypy.request is discarded.
|
||||
url = _urljoin(cherrypy.url(), url)
|
||||
abs_urls.append(url)
|
||||
self.urls = abs_urls
|
||||
|
||||
# RFC 2616 indicates a 301 response code fits our goal; however,
|
||||
# browser support for 301 is quite messy. Do 302/303 instead. See
|
||||
# http://www.alanflavell.org.uk/www/post-redirect.html
|
||||
if status is None:
|
||||
if request.protocol >= (1, 1):
|
||||
status = 303
|
||||
else:
|
||||
status = 302
|
||||
else:
|
||||
status = int(status)
|
||||
if status < 300 or status > 399:
|
||||
raise ValueError("status must be between 300 and 399.")
|
||||
|
||||
self.status = status
|
||||
CherryPyException.__init__(self, abs_urls, status)
|
||||
|
||||
def set_response(self):
|
||||
"""Modify cherrypy.response status, headers, and body to represent
|
||||
self.
|
||||
|
||||
CherryPy uses this internally, but you can also use it to create an
|
||||
HTTPRedirect object and set its output without *raising* the exception.
|
||||
"""
|
||||
import cherrypy
|
||||
response = cherrypy.serving.response
|
||||
response.status = status = self.status
|
||||
|
||||
if status in (300, 301, 302, 303, 307):
|
||||
response.headers['Content-Type'] = "text/html;charset=utf-8"
|
||||
# "The ... URI SHOULD be given by the Location field
|
||||
# in the response."
|
||||
response.headers['Location'] = self.urls[0]
|
||||
|
||||
# "Unless the request method was HEAD, the entity of the response
|
||||
# SHOULD contain a short hypertext note with a hyperlink to the
|
||||
# new URI(s)."
|
||||
msg = {
|
||||
300: "This resource can be found at ",
|
||||
301: "This resource has permanently moved to ",
|
||||
302: "This resource resides temporarily at ",
|
||||
303: "This resource can be found at ",
|
||||
307: "This resource has moved temporarily to ",
|
||||
}[status]
|
||||
msg += '<a href=%s>%s</a>.'
|
||||
msgs = [msg % (saxutils.quoteattr(u), u) for u in self.urls]
|
||||
response.body = ntob("<br />\n".join(msgs), 'utf-8')
|
||||
# Previous code may have set C-L, so we have to reset it
|
||||
# (allow finalize to set it).
|
||||
response.headers.pop('Content-Length', None)
|
||||
elif status == 304:
|
||||
# Not Modified.
|
||||
# "The response MUST include the following header fields:
|
||||
# Date, unless its omission is required by section 14.18.1"
|
||||
# The "Date" header should have been set in Response.__init__
|
||||
|
||||
# "...the response SHOULD NOT include other entity-headers."
|
||||
for key in ('Allow', 'Content-Encoding', 'Content-Language',
|
||||
'Content-Length', 'Content-Location', 'Content-MD5',
|
||||
'Content-Range', 'Content-Type', 'Expires',
|
||||
'Last-Modified'):
|
||||
if key in response.headers:
|
||||
del response.headers[key]
|
||||
|
||||
# "The 304 response MUST NOT contain a message-body."
|
||||
response.body = None
|
||||
# Previous code may have set C-L, so we have to reset it.
|
||||
response.headers.pop('Content-Length', None)
|
||||
elif status == 305:
|
||||
# Use Proxy.
|
||||
# self.urls[0] should be the URI of the proxy.
|
||||
response.headers['Location'] = ntob(self.urls[0], 'utf-8')
|
||||
response.body = None
|
||||
# Previous code may have set C-L, so we have to reset it.
|
||||
response.headers.pop('Content-Length', None)
|
||||
else:
|
||||
raise ValueError("The %s status code is unknown." % status)
|
||||
|
||||
def __call__(self):
|
||||
"""Use this exception as a request.handler (raise self)."""
|
||||
raise self
|
||||
|
||||
|
||||
def clean_headers(status):
|
||||
"""Remove any headers which should not apply to an error response."""
|
||||
import cherrypy
|
||||
|
||||
response = cherrypy.serving.response
|
||||
|
||||
# Remove headers which applied to the original content,
|
||||
# but do not apply to the error page.
|
||||
respheaders = response.headers
|
||||
for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
|
||||
"Vary", "Content-Encoding", "Content-Length", "Expires",
|
||||
"Content-Location", "Content-MD5", "Last-Modified"]:
|
||||
if key in respheaders:
|
||||
del respheaders[key]
|
||||
|
||||
if status != 416:
|
||||
# A server sending a response with status code 416 (Requested
|
||||
# range not satisfiable) SHOULD include a Content-Range field
|
||||
# with a byte-range-resp-spec of "*". The instance-length
|
||||
# specifies the current length of the selected resource.
|
||||
# A response with status code 206 (Partial Content) MUST NOT
|
||||
# include a Content-Range field with a byte-range- resp-spec of "*".
|
||||
if "Content-Range" in respheaders:
|
||||
del respheaders["Content-Range"]
|
||||
|
||||
|
||||
class HTTPError(CherryPyException):
|
||||
|
||||
"""Exception used to return an HTTP error code (4xx-5xx) to the client.
|
||||
|
||||
This exception can be used to automatically send a response using a
|
||||
http status code, with an appropriate error page. It takes an optional
|
||||
``status`` argument (which must be between 400 and 599); it defaults to 500
|
||||
("Internal Server Error"). It also takes an optional ``message`` argument,
|
||||
which will be returned in the response body. See
|
||||
`RFC2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4>`_
|
||||
for a complete list of available error codes and when to use them.
|
||||
|
||||
Examples::
|
||||
|
||||
raise cherrypy.HTTPError(403)
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden", "You are not allowed to access this resource.")
|
||||
"""
|
||||
|
||||
status = None
|
||||
"""The HTTP status code. May be of type int or str (with a Reason-Phrase).
|
||||
"""
|
||||
|
||||
code = None
|
||||
"""The integer HTTP status code."""
|
||||
|
||||
reason = None
|
||||
"""The HTTP Reason-Phrase string."""
|
||||
|
||||
def __init__(self, status=500, message=None):
|
||||
self.status = status
|
||||
try:
|
||||
self.code, self.reason, defaultmsg = _httputil.valid_status(status)
|
||||
except ValueError:
|
||||
raise self.__class__(500, _exc_info()[1].args[0])
|
||||
|
||||
if self.code < 400 or self.code > 599:
|
||||
raise ValueError("status must be between 400 and 599.")
|
||||
|
||||
# See http://www.python.org/dev/peps/pep-0352/
|
||||
# self.message = message
|
||||
self._message = message or defaultmsg
|
||||
CherryPyException.__init__(self, status, message)
|
||||
|
||||
def set_response(self):
|
||||
"""Modify cherrypy.response status, headers, and body to represent
|
||||
self.
|
||||
|
||||
CherryPy uses this internally, but you can also use it to create an
|
||||
HTTPError object and set its output without *raising* the exception.
|
||||
"""
|
||||
import cherrypy
|
||||
|
||||
response = cherrypy.serving.response
|
||||
|
||||
clean_headers(self.code)
|
||||
|
||||
# In all cases, finalize will be called after this method,
|
||||
# so don't bother cleaning up response values here.
|
||||
response.status = self.status
|
||||
tb = None
|
||||
if cherrypy.serving.request.show_tracebacks:
|
||||
tb = format_exc()
|
||||
|
||||
response.headers.pop('Content-Length', None)
|
||||
|
||||
content = self.get_error_page(self.status, traceback=tb,
|
||||
message=self._message)
|
||||
response.body = content
|
||||
|
||||
_be_ie_unfriendly(self.code)
|
||||
|
||||
def get_error_page(self, *args, **kwargs):
|
||||
return get_error_page(*args, **kwargs)
|
||||
|
||||
def __call__(self):
|
||||
"""Use this exception as a request.handler (raise self)."""
|
||||
raise self
|
||||
|
||||
@classmethod
|
||||
@contextlib.contextmanager
|
||||
def handle(cls, exception, status=500, message=''):
|
||||
"""Translate exception into an HTTPError."""
|
||||
try:
|
||||
yield
|
||||
except exception as exc:
|
||||
raise cls(status, message or str(exc))
|
||||
|
||||
|
||||
class NotFound(HTTPError):
|
||||
|
||||
"""Exception raised when a URL could not be mapped to any handler (404).
|
||||
|
||||
This is equivalent to raising
|
||||
:class:`HTTPError("404 Not Found") <cherrypy._cperror.HTTPError>`.
|
||||
"""
|
||||
|
||||
def __init__(self, path=None):
|
||||
if path is None:
|
||||
import cherrypy
|
||||
request = cherrypy.serving.request
|
||||
path = request.script_name + request.path_info
|
||||
self.args = (path,)
|
||||
HTTPError.__init__(self, 404, "The path '%s' was not found." % path)
|
||||
|
||||
|
||||
_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC
|
||||
"-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
|
||||
<title>%(status)s</title>
|
||||
<style type="text/css">
|
||||
#powered_by {
|
||||
margin-top: 20px;
|
||||
border-top: 2px solid black;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#traceback {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>%(status)s</h2>
|
||||
<p>%(message)s</p>
|
||||
<pre id="traceback">%(traceback)s</pre>
|
||||
<div id="powered_by">
|
||||
<span>
|
||||
Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a>
|
||||
</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
|
||||
def get_error_page(status, **kwargs):
|
||||
"""Return an HTML page, containing a pretty error response.
|
||||
|
||||
status should be an int or a str.
|
||||
kwargs will be interpolated into the page template.
|
||||
"""
|
||||
import cherrypy
|
||||
|
||||
try:
|
||||
code, reason, message = _httputil.valid_status(status)
|
||||
except ValueError:
|
||||
raise cherrypy.HTTPError(500, _exc_info()[1].args[0])
|
||||
|
||||
# We can't use setdefault here, because some
|
||||
# callers send None for kwarg values.
|
||||
if kwargs.get('status') is None:
|
||||
kwargs['status'] = "%s %s" % (code, reason)
|
||||
if kwargs.get('message') is None:
|
||||
kwargs['message'] = message
|
||||
if kwargs.get('traceback') is None:
|
||||
kwargs['traceback'] = ''
|
||||
if kwargs.get('version') is None:
|
||||
kwargs['version'] = cherrypy.__version__
|
||||
|
||||
for k, v in iteritems(kwargs):
|
||||
if v is None:
|
||||
kwargs[k] = ""
|
||||
else:
|
||||
kwargs[k] = _escape(kwargs[k])
|
||||
|
||||
# Use a custom template or callable for the error page?
|
||||
pages = cherrypy.serving.request.error_page
|
||||
error_page = pages.get(code) or pages.get('default')
|
||||
|
||||
# Default template, can be overridden below.
|
||||
template = _HTTPErrorTemplate
|
||||
if error_page:
|
||||
try:
|
||||
if hasattr(error_page, '__call__'):
|
||||
# The caller function may be setting headers manually,
|
||||
# so we delegate to it completely. We may be returning
|
||||
# an iterator as well as a string here.
|
||||
#
|
||||
# We *must* make sure any content is not unicode.
|
||||
result = error_page(**kwargs)
|
||||
if cherrypy.lib.is_iterator(result):
|
||||
from cherrypy.lib.encoding import UTF8StreamEncoder
|
||||
return UTF8StreamEncoder(result)
|
||||
elif isinstance(result, six.text_type):
|
||||
return result.encode('utf-8')
|
||||
else:
|
||||
if not isinstance(result, bytes):
|
||||
raise ValueError('error page function did not '
|
||||
'return a bytestring, six.text_typeing or an '
|
||||
'iterator - returned object of type %s.'
|
||||
% (type(result).__name__))
|
||||
return result
|
||||
else:
|
||||
# Load the template from this path.
|
||||
template = tonative(open(error_page, 'rb').read())
|
||||
except:
|
||||
e = _format_exception(*_exc_info())[-1]
|
||||
m = kwargs['message']
|
||||
if m:
|
||||
m += "<br />"
|
||||
m += "In addition, the custom error page failed:\n<br />%s" % e
|
||||
kwargs['message'] = m
|
||||
|
||||
response = cherrypy.serving.response
|
||||
response.headers['Content-Type'] = "text/html;charset=utf-8"
|
||||
result = template % kwargs
|
||||
return result.encode('utf-8')
|
||||
|
||||
|
||||
|
||||
_ie_friendly_error_sizes = {
|
||||
400: 512, 403: 256, 404: 512, 405: 256,
|
||||
406: 512, 408: 512, 409: 512, 410: 256,
|
||||
500: 512, 501: 512, 505: 512,
|
||||
}
|
||||
|
||||
|
||||
def _be_ie_unfriendly(status):
|
||||
import cherrypy
|
||||
response = cherrypy.serving.response
|
||||
|
||||
# For some statuses, Internet Explorer 5+ shows "friendly error
|
||||
# messages" instead of our response.body if the body is smaller
|
||||
# than a given size. Fix this by returning a body over that size
|
||||
# (by adding whitespace).
|
||||
# See http://support.microsoft.com/kb/q218155/
|
||||
s = _ie_friendly_error_sizes.get(status, 0)
|
||||
if s:
|
||||
s += 1
|
||||
# Since we are issuing an HTTP error status, we assume that
|
||||
# the entity is short, and we should just collapse it.
|
||||
content = response.collapse_body()
|
||||
l = len(content)
|
||||
if l and l < s:
|
||||
# IN ADDITION: the response must be written to IE
|
||||
# in one chunk or it will still get replaced! Bah.
|
||||
content = content + (ntob(" ") * (s - l))
|
||||
response.body = content
|
||||
response.headers['Content-Length'] = str(len(content))
|
||||
|
||||
|
||||
def format_exc(exc=None):
|
||||
"""Return exc (or sys.exc_info if None), formatted."""
|
||||
try:
|
||||
if exc is None:
|
||||
exc = _exc_info()
|
||||
if exc == (None, None, None):
|
||||
return ""
|
||||
import traceback
|
||||
return "".join(traceback.format_exception(*exc))
|
||||
finally:
|
||||
del exc
|
||||
|
||||
|
||||
def bare_error(extrabody=None):
|
||||
"""Produce status, headers, body for a critical error.
|
||||
|
||||
Returns a triple without calling any other questionable functions,
|
||||
so it should be as error-free as possible. Call it from an HTTP server
|
||||
if you get errors outside of the request.
|
||||
|
||||
If extrabody is None, a friendly but rather unhelpful error message
|
||||
is set in the body. If extrabody is a string, it will be appended
|
||||
as-is to the body.
|
||||
"""
|
||||
|
||||
# The whole point of this function is to be a last line-of-defense
|
||||
# in handling errors. That is, it must not raise any errors itself;
|
||||
# it cannot be allowed to fail. Therefore, don't add to it!
|
||||
# In particular, don't call any other CP functions.
|
||||
|
||||
body = ntob("Unrecoverable error in the server.")
|
||||
if extrabody is not None:
|
||||
if not isinstance(extrabody, bytes):
|
||||
extrabody = extrabody.encode('utf-8')
|
||||
body += ntob("\n") + extrabody
|
||||
|
||||
return (ntob("500 Internal Server Error"),
|
||||
[(ntob('Content-Type'), ntob('text/plain')),
|
||||
(ntob('Content-Length'), ntob(str(len(body)), 'ISO-8859-1'))],
|
||||
[body])
|
462
deps/cherrypy/_cplogging.py
vendored
Normal file
462
deps/cherrypy/_cplogging.py
vendored
Normal file
@@ -0,0 +1,462 @@
|
||||
"""
|
||||
Simple config
|
||||
=============
|
||||
|
||||
Although CherryPy uses the :mod:`Python logging module <logging>`, it does so
|
||||
behind the scenes so that simple logging is simple, but complicated logging
|
||||
is still possible. "Simple" logging means that you can log to the screen
|
||||
(i.e. console/stdout) or to a file, and that you can easily have separate
|
||||
error and access log files.
|
||||
|
||||
Here are the simplified logging settings. You use these by adding lines to
|
||||
your config file or dict. You should set these at either the global level or
|
||||
per application (see next), but generally not both.
|
||||
|
||||
* ``log.screen``: Set this to True to have both "error" and "access" messages
|
||||
printed to stdout.
|
||||
* ``log.access_file``: Set this to an absolute filename where you want
|
||||
"access" messages written.
|
||||
* ``log.error_file``: Set this to an absolute filename where you want "error"
|
||||
messages written.
|
||||
|
||||
Many events are automatically logged; to log your own application events, call
|
||||
:func:`cherrypy.log`.
|
||||
|
||||
Architecture
|
||||
============
|
||||
|
||||
Separate scopes
|
||||
---------------
|
||||
|
||||
CherryPy provides log managers at both the global and application layers.
|
||||
This means you can have one set of logging rules for your entire site,
|
||||
and another set of rules specific to each application. The global log
|
||||
manager is found at :func:`cherrypy.log`, and the log manager for each
|
||||
application is found at :attr:`app.log<cherrypy._cptree.Application.log>`.
|
||||
If you're inside a request, the latter is reachable from
|
||||
``cherrypy.request.app.log``; if you're outside a request, you'll have to
|
||||
obtain a reference to the ``app``: either the return value of
|
||||
:func:`tree.mount()<cherrypy._cptree.Tree.mount>` or, if you used
|
||||
:func:`quickstart()<cherrypy.quickstart>` instead, via
|
||||
``cherrypy.tree.apps['/']``.
|
||||
|
||||
By default, the global logs are named "cherrypy.error" and "cherrypy.access",
|
||||
and the application logs are named "cherrypy.error.2378745" and
|
||||
"cherrypy.access.2378745" (the number is the id of the Application object).
|
||||
This means that the application logs "bubble up" to the site logs, so if your
|
||||
application has no log handlers, the site-level handlers will still log the
|
||||
messages.
|
||||
|
||||
Errors vs. Access
|
||||
-----------------
|
||||
|
||||
Each log manager handles both "access" messages (one per HTTP request) and
|
||||
"error" messages (everything else). Note that the "error" log is not just for
|
||||
errors! The format of access messages is highly formalized, but the error log
|
||||
isn't--it receives messages from a variety of sources (including full error
|
||||
tracebacks, if enabled).
|
||||
|
||||
If you are logging the access log and error log to the same source, then there
|
||||
is a possibility that a specially crafted error message may replicate an access
|
||||
log message as described in CWE-117. In this case it is the application
|
||||
developer's responsibility to manually escape data before using CherryPy's log()
|
||||
functionality, or they may create an application that is vulnerable to CWE-117.
|
||||
This would be achieved by using a custom handler escape any special characters,
|
||||
and attached as described below.
|
||||
|
||||
Custom Handlers
|
||||
===============
|
||||
|
||||
The simple settings above work by manipulating Python's standard :mod:`logging`
|
||||
module. So when you need something more complex, the full power of the standard
|
||||
module is yours to exploit. You can borrow or create custom handlers, formats,
|
||||
filters, and much more. Here's an example that skips the standard FileHandler
|
||||
and uses a RotatingFileHandler instead:
|
||||
|
||||
::
|
||||
|
||||
#python
|
||||
log = app.log
|
||||
|
||||
# Remove the default FileHandlers if present.
|
||||
log.error_file = ""
|
||||
log.access_file = ""
|
||||
|
||||
maxBytes = getattr(log, "rot_maxBytes", 10000000)
|
||||
backupCount = getattr(log, "rot_backupCount", 1000)
|
||||
|
||||
# Make a new RotatingFileHandler for the error log.
|
||||
fname = getattr(log, "rot_error_file", "error.log")
|
||||
h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
|
||||
h.setLevel(DEBUG)
|
||||
h.setFormatter(_cplogging.logfmt)
|
||||
log.error_log.addHandler(h)
|
||||
|
||||
# Make a new RotatingFileHandler for the access log.
|
||||
fname = getattr(log, "rot_access_file", "access.log")
|
||||
h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
|
||||
h.setLevel(DEBUG)
|
||||
h.setFormatter(_cplogging.logfmt)
|
||||
log.access_log.addHandler(h)
|
||||
|
||||
|
||||
The ``rot_*`` attributes are pulled straight from the application log object.
|
||||
Since "log.*" config entries simply set attributes on the log object, you can
|
||||
add custom attributes to your heart's content. Note that these handlers are
|
||||
used ''instead'' of the default, simple handlers outlined above (so don't set
|
||||
the "log.error_file" config entry, for example).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
||||
logging.Logger.manager.emittedNoHandlerWarning = 1
|
||||
logfmt = logging.Formatter("%(message)s")
|
||||
import os
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import _cperror
|
||||
from cherrypy._cpcompat import ntob
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
|
||||
"""A no-op logging handler to silence the logging.lastResort handler."""
|
||||
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def createLock(self):
|
||||
self.lock = None
|
||||
|
||||
|
||||
class LogManager(object):
|
||||
|
||||
"""An object to assist both simple and advanced logging.
|
||||
|
||||
``cherrypy.log`` is an instance of this class.
|
||||
"""
|
||||
|
||||
appid = None
|
||||
"""The id() of the Application object which owns this log manager. If this
|
||||
is a global log manager, appid is None."""
|
||||
|
||||
error_log = None
|
||||
"""The actual :class:`logging.Logger` instance for error messages."""
|
||||
|
||||
access_log = None
|
||||
"""The actual :class:`logging.Logger` instance for access messages."""
|
||||
|
||||
access_log_format = (
|
||||
'{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
|
||||
if six.PY3 else
|
||||
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
)
|
||||
|
||||
logger_root = None
|
||||
"""The "top-level" logger name.
|
||||
|
||||
This string will be used as the first segment in the Logger names.
|
||||
The default is "cherrypy", for example, in which case the Logger names
|
||||
will be of the form::
|
||||
|
||||
cherrypy.error.<appid>
|
||||
cherrypy.access.<appid>
|
||||
"""
|
||||
|
||||
def __init__(self, appid=None, logger_root="cherrypy"):
|
||||
self.logger_root = logger_root
|
||||
self.appid = appid
|
||||
if appid is None:
|
||||
self.error_log = logging.getLogger("%s.error" % logger_root)
|
||||
self.access_log = logging.getLogger("%s.access" % logger_root)
|
||||
else:
|
||||
self.error_log = logging.getLogger(
|
||||
"%s.error.%s" % (logger_root, appid))
|
||||
self.access_log = logging.getLogger(
|
||||
"%s.access.%s" % (logger_root, appid))
|
||||
self.error_log.setLevel(logging.INFO)
|
||||
self.access_log.setLevel(logging.INFO)
|
||||
|
||||
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
||||
self.error_log.addHandler(NullHandler())
|
||||
self.access_log.addHandler(NullHandler())
|
||||
|
||||
cherrypy.engine.subscribe('graceful', self.reopen_files)
|
||||
|
||||
def reopen_files(self):
|
||||
"""Close and reopen all file handlers."""
|
||||
for log in (self.error_log, self.access_log):
|
||||
for h in log.handlers:
|
||||
if isinstance(h, logging.FileHandler):
|
||||
h.acquire()
|
||||
h.stream.close()
|
||||
h.stream = open(h.baseFilename, h.mode)
|
||||
h.release()
|
||||
|
||||
def error(self, msg='', context='', severity=logging.INFO,
|
||||
traceback=False):
|
||||
"""Write the given ``msg`` to the error log.
|
||||
|
||||
This is not just for errors! Applications may call this at any time
|
||||
to log application-specific information.
|
||||
|
||||
If ``traceback`` is True, the traceback of the current exception
|
||||
(if any) will be appended to ``msg``.
|
||||
"""
|
||||
exc_info = None
|
||||
if traceback:
|
||||
exc_info = _cperror._exc_info()
|
||||
|
||||
self.error_log.log(severity, ' '.join((self.time(), context, msg)), exc_info=exc_info)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""An alias for ``error``."""
|
||||
return self.error(*args, **kwargs)
|
||||
|
||||
def access(self):
|
||||
"""Write to the access log (in Apache/NCSA Combined Log format).
|
||||
|
||||
See the
|
||||
`apache documentation <http://httpd.apache.org/docs/current/logs.html#combined>`_
|
||||
for format details.
|
||||
|
||||
CherryPy calls this automatically for you. Note there are no arguments;
|
||||
it collects the data itself from
|
||||
:class:`cherrypy.request<cherrypy._cprequest.Request>`.
|
||||
|
||||
Like Apache started doing in 2.0.46, non-printable and other special
|
||||
characters in %r (and we expand that to all parts) are escaped using
|
||||
\\xhh sequences, where hh stands for the hexadecimal representation
|
||||
of the raw byte. Exceptions from this rule are " and \\, which are
|
||||
escaped by prepending a backslash, and all whitespace characters,
|
||||
which are written in their C-style notation (\\n, \\t, etc).
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
remote = request.remote
|
||||
response = cherrypy.serving.response
|
||||
outheaders = response.headers
|
||||
inheaders = request.headers
|
||||
if response.output_status is None:
|
||||
status = "-"
|
||||
else:
|
||||
status = response.output_status.split(ntob(" "), 1)[0]
|
||||
if six.PY3:
|
||||
status = status.decode('ISO-8859-1')
|
||||
|
||||
atoms = {'h': remote.name or remote.ip,
|
||||
'l': '-',
|
||||
'u': getattr(request, "login", None) or "-",
|
||||
't': self.time(),
|
||||
'r': request.request_line,
|
||||
's': status,
|
||||
'b': dict.get(outheaders, 'Content-Length', '') or "-",
|
||||
'f': dict.get(inheaders, 'Referer', ''),
|
||||
'a': dict.get(inheaders, 'User-Agent', ''),
|
||||
'o': dict.get(inheaders, 'Host', '-'),
|
||||
}
|
||||
if six.PY3:
|
||||
for k, v in atoms.items():
|
||||
if not isinstance(v, str):
|
||||
v = str(v)
|
||||
v = v.replace('"', '\\"').encode('utf8')
|
||||
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
|
||||
# and backslash for us. All we have to do is strip the quotes.
|
||||
v = repr(v)[2:-1]
|
||||
|
||||
# in python 3.0 the repr of bytes (as returned by encode)
|
||||
# uses double \'s. But then the logger escapes them yet, again
|
||||
# resulting in quadruple slashes. Remove the extra one here.
|
||||
v = v.replace('\\\\', '\\')
|
||||
|
||||
# Escape double-quote.
|
||||
atoms[k] = v
|
||||
|
||||
try:
|
||||
self.access_log.log(
|
||||
logging.INFO, self.access_log_format.format(**atoms))
|
||||
except:
|
||||
self(traceback=True)
|
||||
else:
|
||||
for k, v in atoms.items():
|
||||
if isinstance(v, six.text_type):
|
||||
v = v.encode('utf8')
|
||||
elif not isinstance(v, str):
|
||||
v = str(v)
|
||||
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
|
||||
# and backslash for us. All we have to do is strip the quotes.
|
||||
v = repr(v)[1:-1]
|
||||
# Escape double-quote.
|
||||
atoms[k] = v.replace('"', '\\"')
|
||||
|
||||
try:
|
||||
self.access_log.log(
|
||||
logging.INFO, self.access_log_format % atoms)
|
||||
except:
|
||||
self(traceback=True)
|
||||
|
||||
def time(self):
|
||||
"""Return now() in Apache Common Log Format (no timezone)."""
|
||||
now = datetime.datetime.now()
|
||||
monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
|
||||
'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
|
||||
month = monthnames[now.month - 1].capitalize()
|
||||
return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
|
||||
(now.day, month, now.year, now.hour, now.minute, now.second))
|
||||
|
||||
def _get_builtin_handler(self, log, key):
|
||||
for h in log.handlers:
|
||||
if getattr(h, "_cpbuiltin", None) == key:
|
||||
return h
|
||||
|
||||
# ------------------------- Screen handlers ------------------------- #
|
||||
def _set_screen_handler(self, log, enable, stream=None):
|
||||
h = self._get_builtin_handler(log, "screen")
|
||||
if enable:
|
||||
if not h:
|
||||
if stream is None:
|
||||
stream = sys.stderr
|
||||
h = logging.StreamHandler(stream)
|
||||
h.setFormatter(logfmt)
|
||||
h._cpbuiltin = "screen"
|
||||
log.addHandler(h)
|
||||
elif h:
|
||||
log.handlers.remove(h)
|
||||
|
||||
def _get_screen(self):
|
||||
h = self._get_builtin_handler
|
||||
has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
|
||||
return bool(has_h)
|
||||
|
||||
def _set_screen(self, newvalue):
|
||||
self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
|
||||
self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout)
|
||||
screen = property(_get_screen, _set_screen,
|
||||
doc="""Turn stderr/stdout logging on or off.
|
||||
|
||||
If you set this to True, it'll add the appropriate StreamHandler for
|
||||
you. If you set it to False, it will remove the handler.
|
||||
""")
|
||||
|
||||
# -------------------------- File handlers -------------------------- #
|
||||
|
||||
def _add_builtin_file_handler(self, log, fname):
|
||||
h = logging.FileHandler(fname)
|
||||
h.setFormatter(logfmt)
|
||||
h._cpbuiltin = "file"
|
||||
log.addHandler(h)
|
||||
|
||||
def _set_file_handler(self, log, filename):
|
||||
h = self._get_builtin_handler(log, "file")
|
||||
if filename:
|
||||
if h:
|
||||
if h.baseFilename != os.path.abspath(filename):
|
||||
h.close()
|
||||
log.handlers.remove(h)
|
||||
self._add_builtin_file_handler(log, filename)
|
||||
else:
|
||||
self._add_builtin_file_handler(log, filename)
|
||||
else:
|
||||
if h:
|
||||
h.close()
|
||||
log.handlers.remove(h)
|
||||
|
||||
def _get_error_file(self):
|
||||
h = self._get_builtin_handler(self.error_log, "file")
|
||||
if h:
|
||||
return h.baseFilename
|
||||
return ''
|
||||
|
||||
def _set_error_file(self, newvalue):
|
||||
self._set_file_handler(self.error_log, newvalue)
|
||||
error_file = property(_get_error_file, _set_error_file,
|
||||
doc="""The filename for self.error_log.
|
||||
|
||||
If you set this to a string, it'll add the appropriate FileHandler for
|
||||
you. If you set it to ``None`` or ``''``, it will remove the handler.
|
||||
""")
|
||||
|
||||
def _get_access_file(self):
|
||||
h = self._get_builtin_handler(self.access_log, "file")
|
||||
if h:
|
||||
return h.baseFilename
|
||||
return ''
|
||||
|
||||
def _set_access_file(self, newvalue):
|
||||
self._set_file_handler(self.access_log, newvalue)
|
||||
access_file = property(_get_access_file, _set_access_file,
|
||||
doc="""The filename for self.access_log.
|
||||
|
||||
If you set this to a string, it'll add the appropriate FileHandler for
|
||||
you. If you set it to ``None`` or ``''``, it will remove the handler.
|
||||
""")
|
||||
|
||||
# ------------------------- WSGI handlers ------------------------- #
|
||||
|
||||
def _set_wsgi_handler(self, log, enable):
|
||||
h = self._get_builtin_handler(log, "wsgi")
|
||||
if enable:
|
||||
if not h:
|
||||
h = WSGIErrorHandler()
|
||||
h.setFormatter(logfmt)
|
||||
h._cpbuiltin = "wsgi"
|
||||
log.addHandler(h)
|
||||
elif h:
|
||||
log.handlers.remove(h)
|
||||
|
||||
def _get_wsgi(self):
|
||||
return bool(self._get_builtin_handler(self.error_log, "wsgi"))
|
||||
|
||||
def _set_wsgi(self, newvalue):
|
||||
self._set_wsgi_handler(self.error_log, newvalue)
|
||||
wsgi = property(_get_wsgi, _set_wsgi,
|
||||
doc="""Write errors to wsgi.errors.
|
||||
|
||||
If you set this to True, it'll add the appropriate
|
||||
:class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you
|
||||
(which writes errors to ``wsgi.errors``).
|
||||
If you set it to False, it will remove the handler.
|
||||
""")
|
||||
|
||||
|
||||
class WSGIErrorHandler(logging.Handler):
|
||||
|
||||
"A handler class which writes logging records to environ['wsgi.errors']."
|
||||
|
||||
def flush(self):
|
||||
"""Flushes the stream."""
|
||||
try:
|
||||
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
stream.flush()
|
||||
|
||||
def emit(self, record):
|
||||
"""Emit a record."""
|
||||
try:
|
||||
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
msg = self.format(record)
|
||||
fs = "%s\n"
|
||||
import types
|
||||
# if no unicode support...
|
||||
if not hasattr(types, "UnicodeType"):
|
||||
stream.write(fs % msg)
|
||||
else:
|
||||
try:
|
||||
stream.write(fs % msg)
|
||||
except UnicodeError:
|
||||
stream.write(fs % msg.encode("UTF-8"))
|
||||
self.flush()
|
||||
except:
|
||||
self.handleError(record)
|
354
deps/cherrypy/_cpmodpy.py
vendored
Normal file
354
deps/cherrypy/_cpmodpy.py
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
"""Native adapter for serving CherryPy via mod_python
|
||||
|
||||
Basic usage:
|
||||
|
||||
##########################################
|
||||
# Application in a module called myapp.py
|
||||
##########################################
|
||||
|
||||
import cherrypy
|
||||
|
||||
class Root:
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return 'Hi there, Ho there, Hey there'
|
||||
|
||||
|
||||
# We will use this method from the mod_python configuration
|
||||
# as the entry point to our application
|
||||
def setup_server():
|
||||
cherrypy.tree.mount(Root())
|
||||
cherrypy.config.update({'environment': 'production',
|
||||
'log.screen': False,
|
||||
'show_tracebacks': False})
|
||||
|
||||
##########################################
|
||||
# mod_python settings for apache2
|
||||
# This should reside in your httpd.conf
|
||||
# or a file that will be loaded at
|
||||
# apache startup
|
||||
##########################################
|
||||
|
||||
# Start
|
||||
DocumentRoot "/"
|
||||
Listen 8080
|
||||
LoadModule python_module /usr/lib/apache2/modules/mod_python.so
|
||||
|
||||
<Location "/">
|
||||
PythonPath "sys.path+['/path/to/my/application']"
|
||||
SetHandler python-program
|
||||
PythonHandler cherrypy._cpmodpy::handler
|
||||
PythonOption cherrypy.setup myapp::setup_server
|
||||
PythonDebug On
|
||||
</Location>
|
||||
# End
|
||||
|
||||
The actual path to your mod_python.so is dependent on your
|
||||
environment. In this case we suppose a global mod_python
|
||||
installation on a Linux distribution such as Ubuntu.
|
||||
|
||||
We do set the PythonPath configuration setting so that
|
||||
your application can be found by from the user running
|
||||
the apache2 instance. Of course if your application
|
||||
resides in the global site-package this won't be needed.
|
||||
|
||||
Then restart apache2 and access http://127.0.0.1:8080
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import io
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import copyitems, ntob
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
|
||||
# ------------------------------ Request-handling
|
||||
|
||||
|
||||
def setup(req):
|
||||
from mod_python import apache
|
||||
|
||||
# Run any setup functions defined by a "PythonOption cherrypy.setup"
|
||||
# directive.
|
||||
options = req.get_options()
|
||||
if 'cherrypy.setup' in options:
|
||||
for function in options['cherrypy.setup'].split():
|
||||
atoms = function.split('::', 1)
|
||||
if len(atoms) == 1:
|
||||
mod = __import__(atoms[0], globals(), locals())
|
||||
else:
|
||||
modname, fname = atoms
|
||||
mod = __import__(modname, globals(), locals(), [fname])
|
||||
func = getattr(mod, fname)
|
||||
func()
|
||||
|
||||
cherrypy.config.update({'log.screen': False,
|
||||
"tools.ignore_headers.on": True,
|
||||
"tools.ignore_headers.headers": ['Range'],
|
||||
})
|
||||
|
||||
engine = cherrypy.engine
|
||||
if hasattr(engine, "signal_handler"):
|
||||
engine.signal_handler.unsubscribe()
|
||||
if hasattr(engine, "console_control_handler"):
|
||||
engine.console_control_handler.unsubscribe()
|
||||
engine.autoreload.unsubscribe()
|
||||
cherrypy.server.unsubscribe()
|
||||
|
||||
def _log(msg, level):
|
||||
newlevel = apache.APLOG_ERR
|
||||
if logging.DEBUG >= level:
|
||||
newlevel = apache.APLOG_DEBUG
|
||||
elif logging.INFO >= level:
|
||||
newlevel = apache.APLOG_INFO
|
||||
elif logging.WARNING >= level:
|
||||
newlevel = apache.APLOG_WARNING
|
||||
# On Windows, req.server is required or the msg will vanish. See
|
||||
# http://www.modpython.org/pipermail/mod_python/2003-October/014291.html
|
||||
# Also, "When server is not specified...LogLevel does not apply..."
|
||||
apache.log_error(msg, newlevel, req.server)
|
||||
engine.subscribe('log', _log)
|
||||
|
||||
engine.start()
|
||||
|
||||
def cherrypy_cleanup(data):
|
||||
engine.exit()
|
||||
try:
|
||||
# apache.register_cleanup wasn't available until 3.1.4.
|
||||
apache.register_cleanup(cherrypy_cleanup)
|
||||
except AttributeError:
|
||||
req.server.register_cleanup(req, cherrypy_cleanup)
|
||||
|
||||
|
||||
class _ReadOnlyRequest:
|
||||
expose = ('read', 'readline', 'readlines')
|
||||
|
||||
def __init__(self, req):
|
||||
for method in self.expose:
|
||||
self.__dict__[method] = getattr(req, method)
|
||||
|
||||
|
||||
recursive = False
|
||||
|
||||
_isSetUp = False
|
||||
|
||||
|
||||
def handler(req):
|
||||
from mod_python import apache
|
||||
try:
|
||||
global _isSetUp
|
||||
if not _isSetUp:
|
||||
setup(req)
|
||||
_isSetUp = True
|
||||
|
||||
# Obtain a Request object from CherryPy
|
||||
local = req.connection.local_addr
|
||||
local = httputil.Host(
|
||||
local[0], local[1], req.connection.local_host or "")
|
||||
remote = req.connection.remote_addr
|
||||
remote = httputil.Host(
|
||||
remote[0], remote[1], req.connection.remote_host or "")
|
||||
|
||||
scheme = req.parsed_uri[0] or 'http'
|
||||
req.get_basic_auth_pw()
|
||||
|
||||
try:
|
||||
# apache.mpm_query only became available in mod_python 3.1
|
||||
q = apache.mpm_query
|
||||
threaded = q(apache.AP_MPMQ_IS_THREADED)
|
||||
forked = q(apache.AP_MPMQ_IS_FORKED)
|
||||
except AttributeError:
|
||||
bad_value = ("You must provide a PythonOption '%s', "
|
||||
"either 'on' or 'off', when running a version "
|
||||
"of mod_python < 3.1")
|
||||
|
||||
threaded = options.get('multithread', '').lower()
|
||||
if threaded == 'on':
|
||||
threaded = True
|
||||
elif threaded == 'off':
|
||||
threaded = False
|
||||
else:
|
||||
raise ValueError(bad_value % "multithread")
|
||||
|
||||
forked = options.get('multiprocess', '').lower()
|
||||
if forked == 'on':
|
||||
forked = True
|
||||
elif forked == 'off':
|
||||
forked = False
|
||||
else:
|
||||
raise ValueError(bad_value % "multiprocess")
|
||||
|
||||
sn = cherrypy.tree.script_name(req.uri or "/")
|
||||
if sn is None:
|
||||
send_response(req, '404 Not Found', [], '')
|
||||
else:
|
||||
app = cherrypy.tree.apps[sn]
|
||||
method = req.method
|
||||
path = req.uri
|
||||
qs = req.args or ""
|
||||
reqproto = req.protocol
|
||||
headers = copyitems(req.headers_in)
|
||||
rfile = _ReadOnlyRequest(req)
|
||||
prev = None
|
||||
|
||||
try:
|
||||
redirections = []
|
||||
while True:
|
||||
request, response = app.get_serving(local, remote, scheme,
|
||||
"HTTP/1.1")
|
||||
request.login = req.user
|
||||
request.multithread = bool(threaded)
|
||||
request.multiprocess = bool(forked)
|
||||
request.app = app
|
||||
request.prev = prev
|
||||
|
||||
# Run the CherryPy Request object and obtain the response
|
||||
try:
|
||||
request.run(method, path, qs, reqproto, headers, rfile)
|
||||
break
|
||||
except cherrypy.InternalRedirect:
|
||||
ir = sys.exc_info()[1]
|
||||
app.release_serving()
|
||||
prev = request
|
||||
|
||||
if not recursive:
|
||||
if ir.path in redirections:
|
||||
raise RuntimeError(
|
||||
"InternalRedirector visited the same URL "
|
||||
"twice: %r" % ir.path)
|
||||
else:
|
||||
# Add the *previous* path_info + qs to
|
||||
# redirections.
|
||||
if qs:
|
||||
qs = "?" + qs
|
||||
redirections.append(sn + path + qs)
|
||||
|
||||
# Munge environment and try again.
|
||||
method = "GET"
|
||||
path = ir.path
|
||||
qs = ir.query_string
|
||||
rfile = io.BytesIO()
|
||||
|
||||
send_response(
|
||||
req, response.output_status, response.header_list,
|
||||
response.body, response.stream)
|
||||
finally:
|
||||
app.release_serving()
|
||||
except:
|
||||
tb = format_exc()
|
||||
cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR)
|
||||
s, h, b = bare_error()
|
||||
send_response(req, s, h, b)
|
||||
return apache.OK
|
||||
|
||||
|
||||
def send_response(req, status, headers, body, stream=False):
|
||||
# Set response status
|
||||
req.status = int(status[:3])
|
||||
|
||||
# Set response headers
|
||||
req.content_type = "text/plain"
|
||||
for header, value in headers:
|
||||
if header.lower() == 'content-type':
|
||||
req.content_type = value
|
||||
continue
|
||||
req.headers_out.add(header, value)
|
||||
|
||||
if stream:
|
||||
# Flush now so the status and headers are sent immediately.
|
||||
req.flush()
|
||||
|
||||
# Set response body
|
||||
if isinstance(body, text_or_bytes):
|
||||
req.write(body)
|
||||
else:
|
||||
for seg in body:
|
||||
req.write(seg)
|
||||
|
||||
|
||||
# --------------- Startup tools for CherryPy + mod_python --------------- #
|
||||
import os
|
||||
import re
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
def popen(fullcmd):
|
||||
p = subprocess.Popen(fullcmd, shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
close_fds=True)
|
||||
return p.stdout
|
||||
except ImportError:
|
||||
def popen(fullcmd):
|
||||
pipein, pipeout = os.popen4(fullcmd)
|
||||
return pipeout
|
||||
|
||||
|
||||
def read_process(cmd, args=""):
|
||||
fullcmd = "%s %s" % (cmd, args)
|
||||
pipeout = popen(fullcmd)
|
||||
try:
|
||||
firstline = pipeout.readline()
|
||||
cmd_not_found = re.search(
|
||||
ntob("(not recognized|No such file|not found)"),
|
||||
firstline,
|
||||
re.IGNORECASE
|
||||
)
|
||||
if cmd_not_found:
|
||||
raise IOError('%s must be on your system path.' % cmd)
|
||||
output = firstline + pipeout.read()
|
||||
finally:
|
||||
pipeout.close()
|
||||
return output
|
||||
|
||||
|
||||
class ModPythonServer(object):
|
||||
|
||||
template = """
|
||||
# Apache2 server configuration file for running CherryPy with mod_python.
|
||||
|
||||
DocumentRoot "/"
|
||||
Listen %(port)s
|
||||
LoadModule python_module modules/mod_python.so
|
||||
|
||||
<Location %(loc)s>
|
||||
SetHandler python-program
|
||||
PythonHandler %(handler)s
|
||||
PythonDebug On
|
||||
%(opts)s
|
||||
</Location>
|
||||
"""
|
||||
|
||||
def __init__(self, loc="/", port=80, opts=None, apache_path="apache",
|
||||
handler="cherrypy._cpmodpy::handler"):
|
||||
self.loc = loc
|
||||
self.port = port
|
||||
self.opts = opts
|
||||
self.apache_path = apache_path
|
||||
self.handler = handler
|
||||
|
||||
def start(self):
|
||||
opts = "".join([" PythonOption %s %s\n" % (k, v)
|
||||
for k, v in self.opts])
|
||||
conf_data = self.template % {"port": self.port,
|
||||
"loc": self.loc,
|
||||
"opts": opts,
|
||||
"handler": self.handler,
|
||||
}
|
||||
|
||||
mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf")
|
||||
f = open(mpconf, 'wb')
|
||||
try:
|
||||
f.write(conf_data)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
response = read_process(self.apache_path, "-k start -f %s" % mpconf)
|
||||
self.ready = True
|
||||
return response
|
||||
|
||||
def stop(self):
|
||||
os.popen("apache -k stop")
|
||||
self.ready = False
|
154
deps/cherrypy/_cpnative_server.py
vendored
Normal file
154
deps/cherrypy/_cpnative_server.py
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
"""Native adapter for serving CherryPy via its builtin server."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import io
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class NativeGateway(wsgiserver.Gateway):
|
||||
|
||||
recursive = False
|
||||
|
||||
def respond(self):
|
||||
req = self.req
|
||||
try:
|
||||
# Obtain a Request object from CherryPy
|
||||
local = req.server.bind_addr
|
||||
local = httputil.Host(local[0], local[1], "")
|
||||
remote = req.conn.remote_addr, req.conn.remote_port
|
||||
remote = httputil.Host(remote[0], remote[1], "")
|
||||
|
||||
scheme = req.scheme
|
||||
sn = cherrypy.tree.script_name(req.uri or "/")
|
||||
if sn is None:
|
||||
self.send_response('404 Not Found', [], [''])
|
||||
else:
|
||||
app = cherrypy.tree.apps[sn]
|
||||
method = req.method
|
||||
path = req.path
|
||||
qs = req.qs or ""
|
||||
headers = req.inheaders.items()
|
||||
rfile = req.rfile
|
||||
prev = None
|
||||
|
||||
try:
|
||||
redirections = []
|
||||
while True:
|
||||
request, response = app.get_serving(
|
||||
local, remote, scheme, "HTTP/1.1")
|
||||
request.multithread = True
|
||||
request.multiprocess = False
|
||||
request.app = app
|
||||
request.prev = prev
|
||||
|
||||
# Run the CherryPy Request object and obtain the
|
||||
# response
|
||||
try:
|
||||
request.run(method, path, qs,
|
||||
req.request_protocol, headers, rfile)
|
||||
break
|
||||
except cherrypy.InternalRedirect:
|
||||
ir = sys.exc_info()[1]
|
||||
app.release_serving()
|
||||
prev = request
|
||||
|
||||
if not self.recursive:
|
||||
if ir.path in redirections:
|
||||
raise RuntimeError(
|
||||
"InternalRedirector visited the same "
|
||||
"URL twice: %r" % ir.path)
|
||||
else:
|
||||
# Add the *previous* path_info + qs to
|
||||
# redirections.
|
||||
if qs:
|
||||
qs = "?" + qs
|
||||
redirections.append(sn + path + qs)
|
||||
|
||||
# Munge environment and try again.
|
||||
method = "GET"
|
||||
path = ir.path
|
||||
qs = ir.query_string
|
||||
rfile = io.BytesIO()
|
||||
|
||||
self.send_response(
|
||||
response.output_status, response.header_list,
|
||||
response.body)
|
||||
finally:
|
||||
app.release_serving()
|
||||
except:
|
||||
tb = format_exc()
|
||||
# print tb
|
||||
cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR)
|
||||
s, h, b = bare_error()
|
||||
self.send_response(s, h, b)
|
||||
|
||||
def send_response(self, status, headers, body):
|
||||
req = self.req
|
||||
|
||||
# Set response status
|
||||
req.status = str(status or "500 Server Error")
|
||||
|
||||
# Set response headers
|
||||
for header, value in headers:
|
||||
req.outheaders.append((header, value))
|
||||
if (req.ready and not req.sent_headers):
|
||||
req.sent_headers = True
|
||||
req.send_headers()
|
||||
|
||||
# Set response body
|
||||
for seg in body:
|
||||
req.write(seg)
|
||||
|
||||
|
||||
class CPHTTPServer(wsgiserver.HTTPServer):
|
||||
|
||||
"""Wrapper for wsgiserver.HTTPServer.
|
||||
|
||||
wsgiserver has been designed to not reference CherryPy in any way,
|
||||
so that it can be used in other frameworks and applications.
|
||||
Therefore, we wrap it here, so we can apply some attributes
|
||||
from config -> cherrypy.server -> HTTPServer.
|
||||
"""
|
||||
|
||||
def __init__(self, server_adapter=cherrypy.server):
|
||||
self.server_adapter = server_adapter
|
||||
|
||||
server_name = (self.server_adapter.socket_host or
|
||||
self.server_adapter.socket_file or
|
||||
None)
|
||||
|
||||
wsgiserver.HTTPServer.__init__(
|
||||
self, server_adapter.bind_addr, NativeGateway,
|
||||
minthreads=server_adapter.thread_pool,
|
||||
maxthreads=server_adapter.thread_pool_max,
|
||||
server_name=server_name)
|
||||
|
||||
self.max_request_header_size = (
|
||||
self.server_adapter.max_request_header_size or 0)
|
||||
self.max_request_body_size = (
|
||||
self.server_adapter.max_request_body_size or 0)
|
||||
self.request_queue_size = self.server_adapter.socket_queue_size
|
||||
self.timeout = self.server_adapter.socket_timeout
|
||||
self.shutdown_timeout = self.server_adapter.shutdown_timeout
|
||||
self.protocol = self.server_adapter.protocol_version
|
||||
self.nodelay = self.server_adapter.nodelay
|
||||
|
||||
ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
|
||||
if self.server_adapter.ssl_context:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
self.ssl_adapter.context = self.server_adapter.ssl_context
|
||||
elif self.server_adapter.ssl_certificate:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
1018
deps/cherrypy/_cpreqbody.py
vendored
Normal file
1018
deps/cherrypy/_cpreqbody.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
973
deps/cherrypy/_cprequest.py
vendored
Normal file
973
deps/cherrypy/_cprequest.py
vendored
Normal file
@@ -0,0 +1,973 @@
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import text_or_bytes, copykeys, ntob
|
||||
from cherrypy._cpcompat import SimpleCookie, CookieError
|
||||
from cherrypy import _cpreqbody, _cpconfig
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil, file_generator
|
||||
|
||||
|
||||
class Hook(object):
|
||||
|
||||
"""A callback and its metadata: failsafe, priority, and kwargs."""
|
||||
|
||||
callback = None
|
||||
"""
|
||||
The bare callable that this Hook object is wrapping, which will
|
||||
be called when the Hook is called."""
|
||||
|
||||
failsafe = False
|
||||
"""
|
||||
If True, the callback is guaranteed to run even if other callbacks
|
||||
from the same call point raise exceptions."""
|
||||
|
||||
priority = 50
|
||||
"""
|
||||
Defines the order of execution for a list of Hooks. Priority numbers
|
||||
should be limited to the closed interval [0, 100], but values outside
|
||||
this range are acceptable, as are fractional values."""
|
||||
|
||||
kwargs = {}
|
||||
"""
|
||||
A set of keyword arguments that will be passed to the
|
||||
callable on each call."""
|
||||
|
||||
def __init__(self, callback, failsafe=None, priority=None, **kwargs):
|
||||
self.callback = callback
|
||||
|
||||
if failsafe is None:
|
||||
failsafe = getattr(callback, "failsafe", False)
|
||||
self.failsafe = failsafe
|
||||
|
||||
if priority is None:
|
||||
priority = getattr(callback, "priority", 50)
|
||||
self.priority = priority
|
||||
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __lt__(self, other):
|
||||
# Python 3
|
||||
return self.priority < other.priority
|
||||
|
||||
def __cmp__(self, other):
|
||||
# Python 2
|
||||
return cmp(self.priority, other.priority)
|
||||
|
||||
def __call__(self):
|
||||
"""Run self.callback(**self.kwargs)."""
|
||||
return self.callback(**self.kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
cls = self.__class__
|
||||
return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
|
||||
% (cls.__module__, cls.__name__, self.callback,
|
||||
self.failsafe, self.priority,
|
||||
", ".join(['%s=%r' % (k, v)
|
||||
for k, v in self.kwargs.items()])))
|
||||
|
||||
|
||||
class HookMap(dict):
|
||||
|
||||
"""A map of call points to lists of callbacks (Hook objects)."""
|
||||
|
||||
def __new__(cls, points=None):
|
||||
d = dict.__new__(cls)
|
||||
for p in points or []:
|
||||
d[p] = []
|
||||
return d
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
pass
|
||||
|
||||
def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
|
||||
"""Append a new Hook made from the supplied arguments."""
|
||||
self[point].append(Hook(callback, failsafe, priority, **kwargs))
|
||||
|
||||
def run(self, point):
|
||||
"""Execute all registered Hooks (callbacks) for the given point."""
|
||||
exc = None
|
||||
hooks = self[point]
|
||||
hooks.sort()
|
||||
for hook in hooks:
|
||||
# Some hooks are guaranteed to run even if others at
|
||||
# the same hookpoint fail. We will still log the failure,
|
||||
# but proceed on to the next hook. The only way
|
||||
# to stop all processing from one of these hooks is
|
||||
# to raise SystemExit and stop the whole server.
|
||||
if exc is None or hook.failsafe:
|
||||
try:
|
||||
hook()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
|
||||
cherrypy.InternalRedirect):
|
||||
exc = sys.exc_info()[1]
|
||||
except:
|
||||
exc = sys.exc_info()[1]
|
||||
cherrypy.log(traceback=True, severity=40)
|
||||
if exc:
|
||||
raise exc
|
||||
|
||||
def __copy__(self):
|
||||
newmap = self.__class__()
|
||||
# We can't just use 'update' because we want copies of the
|
||||
# mutable values (each is a list) as well.
|
||||
for k, v in self.items():
|
||||
newmap[k] = v[:]
|
||||
return newmap
|
||||
copy = __copy__
|
||||
|
||||
def __repr__(self):
|
||||
cls = self.__class__
|
||||
return "%s.%s(points=%r)" % (
|
||||
cls.__module__,
|
||||
cls.__name__,
|
||||
copykeys(self)
|
||||
)
|
||||
|
||||
|
||||
# Config namespace handlers
|
||||
|
||||
def hooks_namespace(k, v):
|
||||
"""Attach bare hooks declared in config."""
|
||||
# Use split again to allow multiple hooks for a single
|
||||
# hookpoint per path (e.g. "hooks.before_handler.1").
|
||||
# Little-known fact you only get from reading source ;)
|
||||
hookpoint = k.split(".", 1)[0]
|
||||
if isinstance(v, text_or_bytes):
|
||||
v = cherrypy.lib.attributes(v)
|
||||
if not isinstance(v, Hook):
|
||||
v = Hook(v)
|
||||
cherrypy.serving.request.hooks[hookpoint].append(v)
|
||||
|
||||
|
||||
def request_namespace(k, v):
|
||||
"""Attach request attributes declared in config."""
|
||||
# Provides config entries to set request.body attrs (like
|
||||
# attempt_charsets).
|
||||
if k[:5] == 'body.':
|
||||
setattr(cherrypy.serving.request.body, k[5:], v)
|
||||
else:
|
||||
setattr(cherrypy.serving.request, k, v)
|
||||
|
||||
|
||||
def response_namespace(k, v):
|
||||
"""Attach response attributes declared in config."""
|
||||
# Provides config entries to set default response headers
|
||||
# http://cherrypy.org/ticket/889
|
||||
if k[:8] == 'headers.':
|
||||
cherrypy.serving.response.headers[k.split('.', 1)[1]] = v
|
||||
else:
|
||||
setattr(cherrypy.serving.response, k, v)
|
||||
|
||||
|
||||
def error_page_namespace(k, v):
|
||||
"""Attach error pages declared in config."""
|
||||
if k != 'default':
|
||||
k = int(k)
|
||||
cherrypy.serving.request.error_page[k] = v
|
||||
|
||||
|
||||
hookpoints = ['on_start_resource', 'before_request_body',
|
||||
'before_handler', 'before_finalize',
|
||||
'on_end_resource', 'on_end_request',
|
||||
'before_error_response', 'after_error_response']
|
||||
|
||||
|
||||
class Request(object):
|
||||
|
||||
"""An HTTP request.
|
||||
|
||||
This object represents the metadata of an HTTP request message;
|
||||
that is, it contains attributes which describe the environment
|
||||
in which the request URL, headers, and body were sent (if you
|
||||
want tools to interpret the headers and body, those are elsewhere,
|
||||
mostly in Tools). This 'metadata' consists of socket data,
|
||||
transport characteristics, and the Request-Line. This object
|
||||
also contains data regarding the configuration in effect for
|
||||
the given URL, and the execution plan for generating a response.
|
||||
"""
|
||||
|
||||
prev = None
|
||||
"""
|
||||
The previous Request object (if any). This should be None
|
||||
unless we are processing an InternalRedirect."""
|
||||
|
||||
# Conversation/connection attributes
|
||||
local = httputil.Host("127.0.0.1", 80)
|
||||
"An httputil.Host(ip, port, hostname) object for the server socket."
|
||||
|
||||
remote = httputil.Host("127.0.0.1", 1111)
|
||||
"An httputil.Host(ip, port, hostname) object for the client socket."
|
||||
|
||||
scheme = "http"
|
||||
"""
|
||||
The protocol used between client and server. In most cases,
|
||||
this will be either 'http' or 'https'."""
|
||||
|
||||
server_protocol = "HTTP/1.1"
|
||||
"""
|
||||
The HTTP version for which the HTTP server is at least
|
||||
conditionally compliant."""
|
||||
|
||||
base = ""
|
||||
"""The (scheme://host) portion of the requested URL.
|
||||
In some cases (e.g. when proxying via mod_rewrite), this may contain
|
||||
path segments which cherrypy.url uses when constructing url's, but
|
||||
which otherwise are ignored by CherryPy. Regardless, this value
|
||||
MUST NOT end in a slash."""
|
||||
|
||||
# Request-Line attributes
|
||||
request_line = ""
|
||||
"""
|
||||
The complete Request-Line received from the client. This is a
|
||||
single string consisting of the request method, URI, and protocol
|
||||
version (joined by spaces). Any final CRLF is removed."""
|
||||
|
||||
method = "GET"
|
||||
"""
|
||||
Indicates the HTTP method to be performed on the resource identified
|
||||
by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
|
||||
DELETE. CherryPy allows any extension method; however, various HTTP
|
||||
servers and gateways may restrict the set of allowable methods.
|
||||
CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
|
||||
|
||||
query_string = ""
|
||||
"""
|
||||
The query component of the Request-URI, a string of information to be
|
||||
interpreted by the resource. The query portion of a URI follows the
|
||||
path component, and is separated by a '?'. For example, the URI
|
||||
'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
|
||||
'a=3&b=4'."""
|
||||
|
||||
query_string_encoding = 'utf8'
|
||||
"""
|
||||
The encoding expected for query string arguments after % HEX HEX decoding).
|
||||
If a query string is provided that cannot be decoded with this encoding,
|
||||
404 is raised (since technically it's a different URI). If you want
|
||||
arbitrary encodings to not error, set this to 'Latin-1'; you can then
|
||||
encode back to bytes and re-decode to whatever encoding you like later.
|
||||
"""
|
||||
|
||||
protocol = (1, 1)
|
||||
"""The HTTP protocol version corresponding to the set
|
||||
of features which should be allowed in the response. If BOTH
|
||||
the client's request message AND the server's level of HTTP
|
||||
compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
|
||||
If either is 1.0, this attribute will be the tuple (1, 0).
|
||||
Lower HTTP protocol versions are not explicitly supported."""
|
||||
|
||||
params = {}
|
||||
"""
|
||||
A dict which combines query string (GET) and request entity (POST)
|
||||
variables. This is populated in two stages: GET params are added
|
||||
before the 'on_start_resource' hook, and POST params are added
|
||||
between the 'before_request_body' and 'before_handler' hooks."""
|
||||
|
||||
# Message attributes
|
||||
header_list = []
|
||||
"""
|
||||
A list of the HTTP request headers as (name, value) tuples.
|
||||
In general, you should use request.headers (a dict) instead."""
|
||||
|
||||
headers = httputil.HeaderMap()
|
||||
"""
|
||||
A dict-like object containing the request headers. Keys are header
|
||||
names (in Title-Case format); however, you may get and set them in
|
||||
a case-insensitive manner. That is, headers['Content-Type'] and
|
||||
headers['content-type'] refer to the same value. Values are header
|
||||
values (decoded according to :rfc:`2047` if necessary). See also:
|
||||
httputil.HeaderMap, httputil.HeaderElement."""
|
||||
|
||||
cookie = SimpleCookie()
|
||||
"""See help(Cookie)."""
|
||||
|
||||
rfile = None
|
||||
"""
|
||||
If the request included an entity (body), it will be available
|
||||
as a stream in this attribute. However, the rfile will normally
|
||||
be read for you between the 'before_request_body' hook and the
|
||||
'before_handler' hook, and the resulting string is placed into
|
||||
either request.params or the request.body attribute.
|
||||
|
||||
You may disable the automatic consumption of the rfile by setting
|
||||
request.process_request_body to False, either in config for the desired
|
||||
path, or in an 'on_start_resource' or 'before_request_body' hook.
|
||||
|
||||
WARNING: In almost every case, you should not attempt to read from the
|
||||
rfile stream after CherryPy's automatic mechanism has read it. If you
|
||||
turn off the automatic parsing of rfile, you should read exactly the
|
||||
number of bytes specified in request.headers['Content-Length'].
|
||||
Ignoring either of these warnings may result in a hung request thread
|
||||
or in corruption of the next (pipelined) request.
|
||||
"""
|
||||
|
||||
process_request_body = True
|
||||
"""
|
||||
If True, the rfile (if any) is automatically read and parsed,
|
||||
and the result placed into request.params or request.body."""
|
||||
|
||||
methods_with_bodies = ("POST", "PUT")
|
||||
"""
|
||||
A sequence of HTTP methods for which CherryPy will automatically
|
||||
attempt to read a body from the rfile. If you are going to change
|
||||
this property, modify it on the configuration (recommended)
|
||||
or on the "hook point" `on_start_resource`.
|
||||
"""
|
||||
|
||||
body = None
|
||||
"""
|
||||
If the request Content-Type is 'application/x-www-form-urlencoded'
|
||||
or multipart, this will be None. Otherwise, this will be an instance
|
||||
of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
|
||||
can .read()); this value is set between the 'before_request_body' and
|
||||
'before_handler' hooks (assuming that process_request_body is True)."""
|
||||
|
||||
# Dispatch attributes
|
||||
dispatch = cherrypy.dispatch.Dispatcher()
|
||||
"""
|
||||
The object which looks up the 'page handler' callable and collects
|
||||
config for the current request based on the path_info, other
|
||||
request attributes, and the application architecture. The core
|
||||
calls the dispatcher as early as possible, passing it a 'path_info'
|
||||
argument.
|
||||
|
||||
The default dispatcher discovers the page handler by matching path_info
|
||||
to a hierarchical arrangement of objects, starting at request.app.root.
|
||||
See help(cherrypy.dispatch) for more information."""
|
||||
|
||||
script_name = ""
|
||||
"""
|
||||
The 'mount point' of the application which is handling this request.
|
||||
|
||||
This attribute MUST NOT end in a slash. If the script_name refers to
|
||||
the root of the URI, it MUST be an empty string (not "/").
|
||||
"""
|
||||
|
||||
path_info = "/"
|
||||
"""
|
||||
The 'relative path' portion of the Request-URI. This is relative
|
||||
to the script_name ('mount point') of the application which is
|
||||
handling this request."""
|
||||
|
||||
login = None
|
||||
"""
|
||||
When authentication is used during the request processing this is
|
||||
set to 'False' if it failed and to the 'username' value if it succeeded.
|
||||
The default 'None' implies that no authentication happened."""
|
||||
|
||||
# Note that cherrypy.url uses "if request.app:" to determine whether
|
||||
# the call is during a real HTTP request or not. So leave this None.
|
||||
app = None
|
||||
"""The cherrypy.Application object which is handling this request."""
|
||||
|
||||
handler = None
|
||||
"""
|
||||
The function, method, or other callable which CherryPy will call to
|
||||
produce the response. The discovery of the handler and the arguments
|
||||
it will receive are determined by the request.dispatch object.
|
||||
By default, the handler is discovered by walking a tree of objects
|
||||
starting at request.app.root, and is then passed all HTTP params
|
||||
(from the query string and POST body) as keyword arguments."""
|
||||
|
||||
toolmaps = {}
|
||||
"""
|
||||
A nested dict of all Toolboxes and Tools in effect for this request,
|
||||
of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
|
||||
|
||||
config = None
|
||||
"""
|
||||
A flat dict of all configuration entries which apply to the
|
||||
current request. These entries are collected from global config,
|
||||
application config (based on request.path_info), and from handler
|
||||
config (exactly how is governed by the request.dispatch object in
|
||||
effect for this request; by default, handler config can be attached
|
||||
anywhere in the tree between request.app.root and the final handler,
|
||||
and inherits downward)."""
|
||||
|
||||
is_index = None
|
||||
"""
|
||||
This will be True if the current request is mapped to an 'index'
|
||||
resource handler (also, a 'default' handler if path_info ends with
|
||||
a slash). The value may be used to automatically redirect the
|
||||
user-agent to a 'more canonical' URL which either adds or removes
|
||||
the trailing slash. See cherrypy.tools.trailing_slash."""
|
||||
|
||||
hooks = HookMap(hookpoints)
|
||||
"""
|
||||
A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
|
||||
Each key is a str naming the hook point, and each value is a list
|
||||
of hooks which will be called at that hook point during this request.
|
||||
The list of hooks is generally populated as early as possible (mostly
|
||||
from Tools specified in config), but may be extended at any time.
|
||||
See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
|
||||
|
||||
error_response = cherrypy.HTTPError(500).set_response
|
||||
"""
|
||||
The no-arg callable which will handle unexpected, untrapped errors
|
||||
during request processing. This is not used for expected exceptions
|
||||
(like NotFound, HTTPError, or HTTPRedirect) which are raised in
|
||||
response to expected conditions (those should be customized either
|
||||
via request.error_page or by overriding HTTPError.set_response).
|
||||
By default, error_response uses HTTPError(500) to return a generic
|
||||
error response to the user-agent."""
|
||||
|
||||
error_page = {}
|
||||
"""
|
||||
A dict of {error code: response filename or callable} pairs.
|
||||
|
||||
The error code must be an int representing a given HTTP error code,
|
||||
or the string 'default', which will be used if no matching entry
|
||||
is found for a given numeric code.
|
||||
|
||||
If a filename is provided, the file should contain a Python string-
|
||||
formatting template, and can expect by default to receive format
|
||||
values with the mapping keys %(status)s, %(message)s, %(traceback)s,
|
||||
and %(version)s. The set of format mappings can be extended by
|
||||
overriding HTTPError.set_response.
|
||||
|
||||
If a callable is provided, it will be called by default with keyword
|
||||
arguments 'status', 'message', 'traceback', and 'version', as for a
|
||||
string-formatting template. The callable must return a string or
|
||||
iterable of strings which will be set to response.body. It may also
|
||||
override headers or perform any other processing.
|
||||
|
||||
If no entry is given for an error code, and no 'default' entry exists,
|
||||
a default template will be used.
|
||||
"""
|
||||
|
||||
show_tracebacks = True
|
||||
"""
|
||||
If True, unexpected errors encountered during request processing will
|
||||
include a traceback in the response body."""
|
||||
|
||||
show_mismatched_params = True
|
||||
"""
|
||||
If True, mismatched parameters encountered during PageHandler invocation
|
||||
processing will be included in the response body."""
|
||||
|
||||
throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
|
||||
"""The sequence of exceptions which Request.run does not trap."""
|
||||
|
||||
throw_errors = False
|
||||
"""
|
||||
If True, Request.run will not trap any errors (except HTTPRedirect and
|
||||
HTTPError, which are more properly called 'exceptions', not errors)."""
|
||||
|
||||
closed = False
|
||||
"""True once the close method has been called, False otherwise."""
|
||||
|
||||
stage = None
|
||||
"""
|
||||
A string containing the stage reached in the request-handling process.
|
||||
This is useful when debugging a live server with hung requests."""
|
||||
|
||||
namespaces = _cpconfig.NamespaceSet(
|
||||
**{"hooks": hooks_namespace,
|
||||
"request": request_namespace,
|
||||
"response": response_namespace,
|
||||
"error_page": error_page_namespace,
|
||||
"tools": cherrypy.tools,
|
||||
})
|
||||
|
||||
def __init__(self, local_host, remote_host, scheme="http",
|
||||
server_protocol="HTTP/1.1"):
|
||||
"""Populate a new Request object.
|
||||
|
||||
local_host should be an httputil.Host object with the server info.
|
||||
remote_host should be an httputil.Host object with the client info.
|
||||
scheme should be a string, either "http" or "https".
|
||||
"""
|
||||
self.local = local_host
|
||||
self.remote = remote_host
|
||||
self.scheme = scheme
|
||||
self.server_protocol = server_protocol
|
||||
|
||||
self.closed = False
|
||||
|
||||
# Put a *copy* of the class error_page into self.
|
||||
self.error_page = self.error_page.copy()
|
||||
|
||||
# Put a *copy* of the class namespaces into self.
|
||||
self.namespaces = self.namespaces.copy()
|
||||
|
||||
self.stage = None
|
||||
|
||||
def close(self):
|
||||
"""Run cleanup code. (Core)"""
|
||||
if not self.closed:
|
||||
self.closed = True
|
||||
self.stage = 'on_end_request'
|
||||
self.hooks.run('on_end_request')
|
||||
self.stage = 'close'
|
||||
|
||||
def run(self, method, path, query_string, req_protocol, headers, rfile):
|
||||
r"""Process the Request. (Core)
|
||||
|
||||
method, path, query_string, and req_protocol should be pulled directly
|
||||
from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
|
||||
|
||||
path
|
||||
This should be %XX-unquoted, but query_string should not be.
|
||||
|
||||
When using Python 2, they both MUST be byte strings,
|
||||
not unicode strings.
|
||||
|
||||
When using Python 3, they both MUST be unicode strings,
|
||||
not byte strings, and preferably not bytes \x00-\xFF
|
||||
disguised as unicode.
|
||||
|
||||
headers
|
||||
A list of (name, value) tuples.
|
||||
|
||||
rfile
|
||||
A file-like object containing the HTTP request entity.
|
||||
|
||||
When run() is done, the returned object should have 3 attributes:
|
||||
|
||||
* status, e.g. "200 OK"
|
||||
* header_list, a list of (name, value) tuples
|
||||
* body, an iterable yielding strings
|
||||
|
||||
Consumer code (HTTP servers) should then access these response
|
||||
attributes to build the outbound stream.
|
||||
|
||||
"""
|
||||
response = cherrypy.serving.response
|
||||
self.stage = 'run'
|
||||
try:
|
||||
self.error_response = cherrypy.HTTPError(500).set_response
|
||||
|
||||
self.method = method
|
||||
path = path or "/"
|
||||
self.query_string = query_string or ''
|
||||
self.params = {}
|
||||
|
||||
# Compare request and server HTTP protocol versions, in case our
|
||||
# server does not support the requested protocol. Limit our output
|
||||
# to min(req, server). We want the following output:
|
||||
# request server actual written supported response
|
||||
# protocol protocol response protocol feature set
|
||||
# a 1.0 1.0 1.0 1.0
|
||||
# b 1.0 1.1 1.1 1.0
|
||||
# c 1.1 1.0 1.0 1.0
|
||||
# d 1.1 1.1 1.1 1.1
|
||||
# Notice that, in (b), the response will be "HTTP/1.1" even though
|
||||
# the client only understands 1.0. RFC 2616 10.5.6 says we should
|
||||
# only return 505 if the _major_ version is different.
|
||||
rp = int(req_protocol[5]), int(req_protocol[7])
|
||||
sp = int(self.server_protocol[5]), int(self.server_protocol[7])
|
||||
self.protocol = min(rp, sp)
|
||||
response.headers.protocol = self.protocol
|
||||
|
||||
# Rebuild first line of the request (e.g. "GET /path HTTP/1.0").
|
||||
url = path
|
||||
if query_string:
|
||||
url += '?' + query_string
|
||||
self.request_line = '%s %s %s' % (method, url, req_protocol)
|
||||
|
||||
self.header_list = list(headers)
|
||||
self.headers = httputil.HeaderMap()
|
||||
|
||||
self.rfile = rfile
|
||||
self.body = None
|
||||
|
||||
self.cookie = SimpleCookie()
|
||||
self.handler = None
|
||||
|
||||
# path_info should be the path from the
|
||||
# app root (script_name) to the handler.
|
||||
self.script_name = self.app.script_name
|
||||
self.path_info = pi = path[len(self.script_name):]
|
||||
|
||||
self.stage = 'respond'
|
||||
self.respond(pi)
|
||||
|
||||
except self.throws:
|
||||
raise
|
||||
except:
|
||||
if self.throw_errors:
|
||||
raise
|
||||
else:
|
||||
# Failure in setup, error handler or finalize. Bypass them.
|
||||
# Can't use handle_error because we may not have hooks yet.
|
||||
cherrypy.log(traceback=True, severity=40)
|
||||
if self.show_tracebacks:
|
||||
body = format_exc()
|
||||
else:
|
||||
body = ""
|
||||
r = bare_error(body)
|
||||
response.output_status, response.header_list, response.body = r
|
||||
|
||||
if self.method == "HEAD":
|
||||
# HEAD requests MUST NOT return a message-body in the response.
|
||||
response.body = []
|
||||
|
||||
try:
|
||||
cherrypy.log.access()
|
||||
except:
|
||||
cherrypy.log.error(traceback=True)
|
||||
|
||||
if response.timed_out:
|
||||
raise cherrypy.TimeoutError()
|
||||
|
||||
return response
|
||||
|
||||
# Uncomment for stage debugging
|
||||
# stage = property(lambda self: self._stage, lambda self, v: print(v))
|
||||
|
||||
def respond(self, path_info):
|
||||
"""Generate a response for the resource at self.path_info. (Core)"""
|
||||
response = cherrypy.serving.response
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
if self.app is None:
|
||||
raise cherrypy.NotFound()
|
||||
|
||||
# Get the 'Host' header, so we can HTTPRedirect properly.
|
||||
self.stage = 'process_headers'
|
||||
self.process_headers()
|
||||
|
||||
# Make a copy of the class hooks
|
||||
self.hooks = self.__class__.hooks.copy()
|
||||
self.toolmaps = {}
|
||||
|
||||
self.stage = 'get_resource'
|
||||
self.get_resource(path_info)
|
||||
|
||||
self.body = _cpreqbody.RequestBody(
|
||||
self.rfile, self.headers, request_params=self.params)
|
||||
|
||||
self.namespaces(self.config)
|
||||
|
||||
self.stage = 'on_start_resource'
|
||||
self.hooks.run('on_start_resource')
|
||||
|
||||
# Parse the querystring
|
||||
self.stage = 'process_query_string'
|
||||
self.process_query_string()
|
||||
|
||||
# Process the body
|
||||
if self.process_request_body:
|
||||
if self.method not in self.methods_with_bodies:
|
||||
self.process_request_body = False
|
||||
self.stage = 'before_request_body'
|
||||
self.hooks.run('before_request_body')
|
||||
if self.process_request_body:
|
||||
self.body.process()
|
||||
|
||||
# Run the handler
|
||||
self.stage = 'before_handler'
|
||||
self.hooks.run('before_handler')
|
||||
if self.handler:
|
||||
self.stage = 'handler'
|
||||
response.body = self.handler()
|
||||
|
||||
# Finalize
|
||||
self.stage = 'before_finalize'
|
||||
self.hooks.run('before_finalize')
|
||||
response.finalize()
|
||||
except (cherrypy.HTTPRedirect, cherrypy.HTTPError):
|
||||
inst = sys.exc_info()[1]
|
||||
inst.set_response()
|
||||
self.stage = 'before_finalize (HTTPError)'
|
||||
self.hooks.run('before_finalize')
|
||||
response.finalize()
|
||||
finally:
|
||||
self.stage = 'on_end_resource'
|
||||
self.hooks.run('on_end_resource')
|
||||
except self.throws:
|
||||
raise
|
||||
except:
|
||||
if self.throw_errors:
|
||||
raise
|
||||
self.handle_error()
|
||||
|
||||
def process_query_string(self):
|
||||
"""Parse the query string into Python structures. (Core)"""
|
||||
try:
|
||||
p = httputil.parse_query_string(
|
||||
self.query_string, encoding=self.query_string_encoding)
|
||||
except UnicodeDecodeError:
|
||||
raise cherrypy.HTTPError(
|
||||
404, "The given query string could not be processed. Query "
|
||||
"strings for this resource must be encoded with %r." %
|
||||
self.query_string_encoding)
|
||||
|
||||
# Python 2 only: keyword arguments must be byte strings (type 'str').
|
||||
if six.PY2:
|
||||
for key, value in p.items():
|
||||
if isinstance(key, six.text_type):
|
||||
del p[key]
|
||||
p[key.encode(self.query_string_encoding)] = value
|
||||
self.params.update(p)
|
||||
|
||||
def process_headers(self):
|
||||
"""Parse HTTP header data into Python structures. (Core)"""
|
||||
# Process the headers into self.headers
|
||||
headers = self.headers
|
||||
for name, value in self.header_list:
|
||||
# Call title() now (and use dict.__method__(headers))
|
||||
# so title doesn't have to be called twice.
|
||||
name = name.title()
|
||||
value = value.strip()
|
||||
|
||||
# Warning: if there is more than one header entry for cookies
|
||||
# (AFAIK, only Konqueror does that), only the last one will
|
||||
# remain in headers (but they will be correctly stored in
|
||||
# request.cookie).
|
||||
if "=?" in value:
|
||||
dict.__setitem__(headers, name, httputil.decode_TEXT(value))
|
||||
else:
|
||||
dict.__setitem__(headers, name, value)
|
||||
|
||||
# Handle cookies differently because on Konqueror, multiple
|
||||
# cookies come on different lines with the same key
|
||||
if name == 'Cookie':
|
||||
try:
|
||||
self.cookie.load(value)
|
||||
except CookieError:
|
||||
msg = "Illegal cookie name %s" % value.split('=')[0]
|
||||
raise cherrypy.HTTPError(400, msg)
|
||||
|
||||
if not dict.__contains__(headers, 'Host'):
|
||||
# All Internet-based HTTP/1.1 servers MUST respond with a 400
|
||||
# (Bad Request) status code to any HTTP/1.1 request message
|
||||
# which lacks a Host header field.
|
||||
if self.protocol >= (1, 1):
|
||||
msg = "HTTP/1.1 requires a 'Host' request header."
|
||||
raise cherrypy.HTTPError(400, msg)
|
||||
host = dict.get(headers, 'Host')
|
||||
if not host:
|
||||
host = self.local.name or self.local.ip
|
||||
self.base = "%s://%s" % (self.scheme, host)
|
||||
|
||||
def get_resource(self, path):
|
||||
"""Call a dispatcher (which sets self.handler and .config). (Core)"""
|
||||
# First, see if there is a custom dispatch at this URI. Custom
|
||||
# dispatchers can only be specified in app.config, not in _cp_config
|
||||
# (since custom dispatchers may not even have an app.root).
|
||||
dispatch = self.app.find_config(
|
||||
path, "request.dispatch", self.dispatch)
|
||||
|
||||
# dispatch() should set self.handler and self.config
|
||||
dispatch(path)
|
||||
|
||||
def handle_error(self):
|
||||
"""Handle the last unanticipated exception. (Core)"""
|
||||
try:
|
||||
self.hooks.run("before_error_response")
|
||||
if self.error_response:
|
||||
self.error_response()
|
||||
self.hooks.run("after_error_response")
|
||||
cherrypy.serving.response.finalize()
|
||||
except cherrypy.HTTPRedirect:
|
||||
inst = sys.exc_info()[1]
|
||||
inst.set_response()
|
||||
cherrypy.serving.response.finalize()
|
||||
|
||||
# ------------------------- Properties ------------------------- #
|
||||
|
||||
def _get_body_params(self):
|
||||
warnings.warn(
|
||||
"body_params is deprecated in CherryPy 3.2, will be removed in "
|
||||
"CherryPy 3.3.",
|
||||
DeprecationWarning
|
||||
)
|
||||
return self.body.params
|
||||
body_params = property(_get_body_params,
|
||||
doc="""
|
||||
If the request Content-Type is 'application/x-www-form-urlencoded' or
|
||||
multipart, this will be a dict of the params pulled from the entity
|
||||
body; that is, it will be the portion of request.params that come
|
||||
from the message body (sometimes called "POST params", although they
|
||||
can be sent with various HTTP method verbs). This value is set between
|
||||
the 'before_request_body' and 'before_handler' hooks (assuming that
|
||||
process_request_body is True).
|
||||
|
||||
Deprecated in 3.2, will be removed for 3.3 in favor of
|
||||
:attr:`request.body.params<cherrypy._cprequest.RequestBody.params>`.""")
|
||||
|
||||
|
||||
class ResponseBody(object):
|
||||
|
||||
"""The body of the HTTP response (the response entity)."""
|
||||
|
||||
if six.PY3:
|
||||
unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
|
||||
"if you wish to return unicode.")
|
||||
|
||||
def __get__(self, obj, objclass=None):
|
||||
if obj is None:
|
||||
# When calling on the class instead of an instance...
|
||||
return self
|
||||
else:
|
||||
return obj._body
|
||||
|
||||
def __set__(self, obj, value):
|
||||
# Convert the given value to an iterable object.
|
||||
if six.PY3 and isinstance(value, str):
|
||||
raise ValueError(self.unicode_err)
|
||||
|
||||
if isinstance(value, text_or_bytes):
|
||||
# strings get wrapped in a list because iterating over a single
|
||||
# item list is much faster than iterating over every character
|
||||
# in a long string.
|
||||
if value:
|
||||
value = [value]
|
||||
else:
|
||||
# [''] doesn't evaluate to False, so replace it with [].
|
||||
value = []
|
||||
elif six.PY3 and isinstance(value, list):
|
||||
# every item in a list must be bytes...
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, str):
|
||||
raise ValueError(self.unicode_err)
|
||||
# Don't use isinstance here; io.IOBase which has an ABC takes
|
||||
# 1000 times as long as, say, isinstance(value, str)
|
||||
elif hasattr(value, 'read'):
|
||||
value = file_generator(value)
|
||||
elif value is None:
|
||||
value = []
|
||||
obj._body = value
|
||||
|
||||
|
||||
class Response(object):
|
||||
|
||||
"""An HTTP Response, including status, headers, and body."""
|
||||
|
||||
status = ""
|
||||
"""The HTTP Status-Code and Reason-Phrase."""
|
||||
|
||||
header_list = []
|
||||
"""
|
||||
A list of the HTTP response headers as (name, value) tuples.
|
||||
In general, you should use response.headers (a dict) instead. This
|
||||
attribute is generated from response.headers and is not valid until
|
||||
after the finalize phase."""
|
||||
|
||||
headers = httputil.HeaderMap()
|
||||
"""
|
||||
A dict-like object containing the response headers. Keys are header
|
||||
names (in Title-Case format); however, you may get and set them in
|
||||
a case-insensitive manner. That is, headers['Content-Type'] and
|
||||
headers['content-type'] refer to the same value. Values are header
|
||||
values (decoded according to :rfc:`2047` if necessary).
|
||||
|
||||
.. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement`
|
||||
"""
|
||||
|
||||
cookie = SimpleCookie()
|
||||
"""See help(Cookie)."""
|
||||
|
||||
body = ResponseBody()
|
||||
"""The body (entity) of the HTTP response."""
|
||||
|
||||
time = None
|
||||
"""The value of time.time() when created. Use in HTTP dates."""
|
||||
|
||||
timeout = 300
|
||||
"""Seconds after which the response will be aborted."""
|
||||
|
||||
timed_out = False
|
||||
"""
|
||||
Flag to indicate the response should be aborted, because it has
|
||||
exceeded its timeout."""
|
||||
|
||||
stream = False
|
||||
"""If False, buffer the response body."""
|
||||
|
||||
def __init__(self):
|
||||
self.status = None
|
||||
self.header_list = None
|
||||
self._body = []
|
||||
self.time = time.time()
|
||||
|
||||
self.headers = httputil.HeaderMap()
|
||||
# Since we know all our keys are titled strings, we can
|
||||
# bypass HeaderMap.update and get a big speed boost.
|
||||
dict.update(self.headers, {
|
||||
"Content-Type": 'text/html',
|
||||
"Server": "CherryPy/" + cherrypy.__version__,
|
||||
"Date": httputil.HTTPDate(self.time),
|
||||
})
|
||||
self.cookie = SimpleCookie()
|
||||
|
||||
def collapse_body(self):
|
||||
"""Collapse self.body to a single string; replace it and return it."""
|
||||
if isinstance(self.body, text_or_bytes):
|
||||
return self.body
|
||||
|
||||
newbody = []
|
||||
for chunk in self.body:
|
||||
if six.PY3 and not isinstance(chunk, bytes):
|
||||
raise TypeError("Chunk %s is not of type 'bytes'." %
|
||||
repr(chunk))
|
||||
newbody.append(chunk)
|
||||
newbody = ntob('').join(newbody)
|
||||
|
||||
self.body = newbody
|
||||
return newbody
|
||||
|
||||
def finalize(self):
|
||||
"""Transform headers (and cookies) into self.header_list. (Core)"""
|
||||
try:
|
||||
code, reason, _ = httputil.valid_status(self.status)
|
||||
except ValueError:
|
||||
raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])
|
||||
|
||||
headers = self.headers
|
||||
|
||||
self.status = "%s %s" % (code, reason)
|
||||
self.output_status = ntob(str(code), 'ascii') + \
|
||||
ntob(" ") + headers.encode(reason)
|
||||
|
||||
if self.stream:
|
||||
# The upshot: wsgiserver will chunk the response if
|
||||
# you pop Content-Length (or set it explicitly to None).
|
||||
# Note that lib.static sets C-L to the file's st_size.
|
||||
if dict.get(headers, 'Content-Length') is None:
|
||||
dict.pop(headers, 'Content-Length', None)
|
||||
elif code < 200 or code in (204, 205, 304):
|
||||
# "All 1xx (informational), 204 (no content),
|
||||
# and 304 (not modified) responses MUST NOT
|
||||
# include a message-body."
|
||||
dict.pop(headers, 'Content-Length', None)
|
||||
self.body = ntob("")
|
||||
else:
|
||||
# Responses which are not streamed should have a Content-Length,
|
||||
# but allow user code to set Content-Length if desired.
|
||||
if dict.get(headers, 'Content-Length') is None:
|
||||
content = self.collapse_body()
|
||||
dict.__setitem__(headers, 'Content-Length', len(content))
|
||||
|
||||
# Transform our header dict into a list of tuples.
|
||||
self.header_list = h = headers.output()
|
||||
|
||||
cookie = self.cookie.output()
|
||||
if cookie:
|
||||
for line in cookie.split("\n"):
|
||||
if line.endswith("\r"):
|
||||
# Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
|
||||
line = line[:-1]
|
||||
name, value = line.split(": ", 1)
|
||||
if isinstance(name, six.text_type):
|
||||
name = name.encode("ISO-8859-1")
|
||||
if isinstance(value, six.text_type):
|
||||
value = headers.encode(value)
|
||||
h.append((name, value))
|
||||
|
||||
def check_timeout(self):
|
||||
"""If now > self.time + self.timeout, set self.timed_out.
|
||||
|
||||
This purposefully sets a flag, rather than raising an error,
|
||||
so that a monitor thread can interrupt the Response thread.
|
||||
"""
|
||||
if time.time() > self.time + self.timeout:
|
||||
self.timed_out = True
|
226
deps/cherrypy/_cpserver.py
vendored
Normal file
226
deps/cherrypy/_cpserver.py
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
"""Manage HTTP servers with CherryPy."""
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.lib.reprconf import attributes
|
||||
from cherrypy._cpcompat import text_or_bytes
|
||||
|
||||
# We import * because we want to export check_port
|
||||
# et al as attributes of this module.
|
||||
from cherrypy.process.servers import *
|
||||
|
||||
|
||||
class Server(ServerAdapter):
|
||||
|
||||
"""An adapter for an HTTP server.
|
||||
|
||||
You can set attributes (like socket_host and socket_port)
|
||||
on *this* object (which is probably cherrypy.server), and call
|
||||
quickstart. For example::
|
||||
|
||||
cherrypy.server.socket_port = 80
|
||||
cherrypy.quickstart()
|
||||
"""
|
||||
|
||||
socket_port = 8080
|
||||
"""The TCP port on which to listen for connections."""
|
||||
|
||||
_socket_host = '127.0.0.1'
|
||||
|
||||
def _get_socket_host(self):
|
||||
return self._socket_host
|
||||
|
||||
def _set_socket_host(self, value):
|
||||
if value == '':
|
||||
raise ValueError("The empty string ('') is not an allowed value. "
|
||||
"Use '0.0.0.0' instead to listen on all active "
|
||||
"interfaces (INADDR_ANY).")
|
||||
self._socket_host = value
|
||||
socket_host = property(
|
||||
_get_socket_host,
|
||||
_set_socket_host,
|
||||
doc="""The hostname or IP address on which to listen for connections.
|
||||
|
||||
Host values may be any IPv4 or IPv6 address, or any valid hostname.
|
||||
The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if
|
||||
your hosts file prefers IPv6). The string '0.0.0.0' is a special
|
||||
IPv4 entry meaning "any active interface" (INADDR_ANY), and '::'
|
||||
is the similar IN6ADDR_ANY for IPv6. The empty string or None are
|
||||
not allowed.""")
|
||||
|
||||
socket_file = None
|
||||
"""If given, the name of the UNIX socket to use instead of TCP/IP.
|
||||
|
||||
When this option is not None, the `socket_host` and `socket_port` options
|
||||
are ignored."""
|
||||
|
||||
socket_queue_size = 5
|
||||
"""The 'backlog' argument to socket.listen(); specifies the maximum number
|
||||
of queued connections (default 5)."""
|
||||
|
||||
socket_timeout = 10
|
||||
"""The timeout in seconds for accepted connections (default 10)."""
|
||||
|
||||
accepted_queue_size = -1
|
||||
"""The maximum number of requests which will be queued up before
|
||||
the server refuses to accept it (default -1, meaning no limit)."""
|
||||
|
||||
accepted_queue_timeout = 10
|
||||
"""The timeout in seconds for attempting to add a request to the
|
||||
queue when the queue is full (default 10)."""
|
||||
|
||||
shutdown_timeout = 5
|
||||
"""The time to wait for HTTP worker threads to clean up."""
|
||||
|
||||
protocol_version = 'HTTP/1.1'
|
||||
"""The version string to write in the Status-Line of all HTTP responses,
|
||||
for example, "HTTP/1.1" (the default). Depending on the HTTP server used,
|
||||
this should also limit the supported features used in the response."""
|
||||
|
||||
thread_pool = 10
|
||||
"""The number of worker threads to start up in the pool."""
|
||||
|
||||
thread_pool_max = -1
|
||||
"""The maximum size of the worker-thread pool. Use -1 to indicate no limit.
|
||||
"""
|
||||
|
||||
max_request_header_size = 500 * 1024
|
||||
"""The maximum number of bytes allowable in the request headers.
|
||||
If exceeded, the HTTP server should return "413 Request Entity Too Large".
|
||||
"""
|
||||
|
||||
max_request_body_size = 100 * 1024 * 1024
|
||||
"""The maximum number of bytes allowable in the request body. If exceeded,
|
||||
the HTTP server should return "413 Request Entity Too Large"."""
|
||||
|
||||
instance = None
|
||||
"""If not None, this should be an HTTP server instance (such as
|
||||
CPWSGIServer) which cherrypy.server will control. Use this when you need
|
||||
more control over object instantiation than is available in the various
|
||||
configuration options."""
|
||||
|
||||
ssl_context = None
|
||||
"""When using PyOpenSSL, an instance of SSL.Context."""
|
||||
|
||||
ssl_certificate = None
|
||||
"""The filename of the SSL certificate to use."""
|
||||
|
||||
ssl_certificate_chain = None
|
||||
"""When using PyOpenSSL, the certificate chain to pass to
|
||||
Context.load_verify_locations."""
|
||||
|
||||
ssl_private_key = None
|
||||
"""The filename of the private key to use with SSL."""
|
||||
|
||||
if six.PY3:
|
||||
ssl_module = 'builtin'
|
||||
"""The name of a registered SSL adaptation module to use with
|
||||
the builtin WSGI server. Builtin options are: 'builtin' (to
|
||||
use the SSL library built into recent versions of Python).
|
||||
You may also register your own classes in the
|
||||
wsgiserver.ssl_adapters dict."""
|
||||
else:
|
||||
ssl_module = 'pyopenssl'
|
||||
"""The name of a registered SSL adaptation module to use with the
|
||||
builtin WSGI server. Builtin options are 'builtin' (to use the SSL
|
||||
library built into recent versions of Python) and 'pyopenssl' (to
|
||||
use the PyOpenSSL project, which you must install separately). You
|
||||
may also register your own classes in the wsgiserver.ssl_adapters
|
||||
dict."""
|
||||
|
||||
statistics = False
|
||||
"""Turns statistics-gathering on or off for aware HTTP servers."""
|
||||
|
||||
nodelay = True
|
||||
"""If True (the default since 3.1), sets the TCP_NODELAY socket option."""
|
||||
|
||||
wsgi_version = (1, 0)
|
||||
"""The WSGI version tuple to use with the builtin WSGI server.
|
||||
The provided options are (1, 0) [which includes support for PEP 3333,
|
||||
which declares it covers WSGI version 1.0.1 but still mandates the
|
||||
wsgi.version (1, 0)] and ('u', 0), an experimental unicode version.
|
||||
You may create and register your own experimental versions of the WSGI
|
||||
protocol by adding custom classes to the wsgiserver.wsgi_gateways dict."""
|
||||
|
||||
def __init__(self):
|
||||
self.bus = cherrypy.engine
|
||||
self.httpserver = None
|
||||
self.interrupt = None
|
||||
self.running = False
|
||||
|
||||
def httpserver_from_self(self, httpserver=None):
|
||||
"""Return a (httpserver, bind_addr) pair based on self attributes."""
|
||||
if httpserver is None:
|
||||
httpserver = self.instance
|
||||
if httpserver is None:
|
||||
from cherrypy import _cpwsgi_server
|
||||
httpserver = _cpwsgi_server.CPWSGIServer(self)
|
||||
if isinstance(httpserver, text_or_bytes):
|
||||
# Is anyone using this? Can I add an arg?
|
||||
httpserver = attributes(httpserver)(self)
|
||||
return httpserver, self.bind_addr
|
||||
|
||||
def start(self):
|
||||
"""Start the HTTP server."""
|
||||
if not self.httpserver:
|
||||
self.httpserver, self.bind_addr = self.httpserver_from_self()
|
||||
ServerAdapter.start(self)
|
||||
start.priority = 75
|
||||
|
||||
def _get_bind_addr(self):
|
||||
if self.socket_file:
|
||||
return self.socket_file
|
||||
if self.socket_host is None and self.socket_port is None:
|
||||
return None
|
||||
return (self.socket_host, self.socket_port)
|
||||
|
||||
def _set_bind_addr(self, value):
|
||||
if value is None:
|
||||
self.socket_file = None
|
||||
self.socket_host = None
|
||||
self.socket_port = None
|
||||
elif isinstance(value, text_or_bytes):
|
||||
self.socket_file = value
|
||||
self.socket_host = None
|
||||
self.socket_port = None
|
||||
else:
|
||||
try:
|
||||
self.socket_host, self.socket_port = value
|
||||
self.socket_file = None
|
||||
except ValueError:
|
||||
raise ValueError("bind_addr must be a (host, port) tuple "
|
||||
"(for TCP sockets) or a string (for Unix "
|
||||
"domain sockets), not %r" % value)
|
||||
bind_addr = property(
|
||||
_get_bind_addr,
|
||||
_set_bind_addr,
|
||||
doc='A (host, port) tuple for TCP sockets or '
|
||||
'a str for Unix domain sockets.')
|
||||
|
||||
def base(self):
|
||||
"""Return the base (scheme://host[:port] or sock file) for this server.
|
||||
"""
|
||||
if self.socket_file:
|
||||
return self.socket_file
|
||||
|
||||
host = self.socket_host
|
||||
if host in ('0.0.0.0', '::'):
|
||||
# 0.0.0.0 is INADDR_ANY and :: is IN6ADDR_ANY.
|
||||
# Look up the host name, which should be the
|
||||
# safest thing to spit out in a URL.
|
||||
import socket
|
||||
host = socket.gethostname()
|
||||
|
||||
port = self.socket_port
|
||||
|
||||
if self.ssl_certificate:
|
||||
scheme = "https"
|
||||
if port != 443:
|
||||
host += ":%s" % port
|
||||
else:
|
||||
scheme = "http"
|
||||
if port != 80:
|
||||
host += ":%s" % port
|
||||
|
||||
return "%s://%s" % (scheme, host)
|
241
deps/cherrypy/_cpthreadinglocal.py
vendored
Normal file
241
deps/cherrypy/_cpthreadinglocal.py
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
# This is a backport of Python-2.4's threading.local() implementation
|
||||
|
||||
"""Thread-local objects
|
||||
|
||||
(Note that this module provides a Python version of thread
|
||||
threading.local class. Depending on the version of Python you're
|
||||
using, there may be a faster one available. You should always import
|
||||
the local class from threading.)
|
||||
|
||||
Thread-local objects support the management of thread-local data.
|
||||
If you have data that you want to be local to a thread, simply create
|
||||
a thread-local object and use its attributes:
|
||||
|
||||
>>> mydata = local()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
You can also access the local-object's dictionary:
|
||||
|
||||
>>> mydata.__dict__
|
||||
{'number': 42}
|
||||
>>> mydata.__dict__.setdefault('widgets', [])
|
||||
[]
|
||||
>>> mydata.widgets
|
||||
[]
|
||||
|
||||
What's important about thread-local objects is that their data are
|
||||
local to a thread. If we access the data in a different thread:
|
||||
|
||||
>>> log = []
|
||||
>>> def f():
|
||||
... items = mydata.__dict__.items()
|
||||
... items.sort()
|
||||
... log.append(items)
|
||||
... mydata.number = 11
|
||||
... log.append(mydata.number)
|
||||
|
||||
>>> import threading
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[], 11]
|
||||
|
||||
we get different data. Furthermore, changes made in the other thread
|
||||
don't affect data seen in this thread:
|
||||
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
Of course, values you get from a local object, including a __dict__
|
||||
attribute, are for whatever thread was current at the time the
|
||||
attribute was read. For that reason, you generally don't want to save
|
||||
these values across threads, as they apply only to the thread they
|
||||
came from.
|
||||
|
||||
You can create custom local objects by subclassing the local class:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... number = 2
|
||||
... initialized = False
|
||||
... def __init__(self, **kw):
|
||||
... if self.initialized:
|
||||
... raise SystemError('__init__ called too many times')
|
||||
... self.initialized = True
|
||||
... self.__dict__.update(kw)
|
||||
... def squared(self):
|
||||
... return self.number ** 2
|
||||
|
||||
This can be useful to support default values, methods and
|
||||
initialization. Note that if you define an __init__ method, it will be
|
||||
called each time the local object is used in a separate thread. This
|
||||
is necessary to initialize each thread's dictionary.
|
||||
|
||||
Now if we create a local object:
|
||||
|
||||
>>> mydata = MyLocal(color='red')
|
||||
|
||||
Now we have a default number:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
|
||||
an initial color:
|
||||
|
||||
>>> mydata.color
|
||||
'red'
|
||||
>>> del mydata.color
|
||||
|
||||
And a method that operates on the data:
|
||||
|
||||
>>> mydata.squared()
|
||||
4
|
||||
|
||||
As before, we can access the data in a separate thread:
|
||||
|
||||
>>> log = []
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[('color', 'red'), ('initialized', True)], 11]
|
||||
|
||||
without affecting this thread's data:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
>>> mydata.color
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'MyLocal' object has no attribute 'color'
|
||||
|
||||
Note that subclasses can define slots, but they are not thread
|
||||
local. They are shared across threads:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... __slots__ = 'number'
|
||||
|
||||
>>> mydata = MyLocal()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.color = 'red'
|
||||
|
||||
So, the separate thread:
|
||||
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
|
||||
affects what we see:
|
||||
|
||||
>>> mydata.number
|
||||
11
|
||||
|
||||
>>> del mydata
|
||||
"""
|
||||
|
||||
# Threading import is at end
|
||||
|
||||
|
||||
class _localbase(object):
|
||||
__slots__ = '_local__key', '_local__args', '_local__lock'
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
self = object.__new__(cls)
|
||||
key = 'thread.local.' + str(id(self))
|
||||
object.__setattr__(self, '_local__key', key)
|
||||
object.__setattr__(self, '_local__args', (args, kw))
|
||||
object.__setattr__(self, '_local__lock', RLock())
|
||||
|
||||
if args or kw and (cls.__init__ is object.__init__):
|
||||
raise TypeError("Initialization arguments are not supported")
|
||||
|
||||
# We need to create the thread dict in anticipation of
|
||||
# __init__ being called, to make sure we don't call it
|
||||
# again ourselves.
|
||||
dict = object.__getattribute__(self, '__dict__')
|
||||
currentThread().__dict__[key] = dict
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def _patch(self):
|
||||
key = object.__getattribute__(self, '_local__key')
|
||||
d = currentThread().__dict__.get(key)
|
||||
if d is None:
|
||||
d = {}
|
||||
currentThread().__dict__[key] = d
|
||||
object.__setattr__(self, '__dict__', d)
|
||||
|
||||
# we have a new instance dict, so call out __init__ if we have
|
||||
# one
|
||||
cls = type(self)
|
||||
if cls.__init__ is not object.__init__:
|
||||
args, kw = object.__getattribute__(self, '_local__args')
|
||||
cls.__init__(self, *args, **kw)
|
||||
else:
|
||||
object.__setattr__(self, '__dict__', d)
|
||||
|
||||
|
||||
class local(_localbase):
|
||||
|
||||
def __getattribute__(self, name):
|
||||
lock = object.__getattribute__(self, '_local__lock')
|
||||
lock.acquire()
|
||||
try:
|
||||
_patch(self)
|
||||
return object.__getattribute__(self, name)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
lock = object.__getattribute__(self, '_local__lock')
|
||||
lock.acquire()
|
||||
try:
|
||||
_patch(self)
|
||||
return object.__setattr__(self, name, value)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __delattr__(self, name):
|
||||
lock = object.__getattribute__(self, '_local__lock')
|
||||
lock.acquire()
|
||||
try:
|
||||
_patch(self)
|
||||
return object.__delattr__(self, name)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __del__():
|
||||
threading_enumerate = enumerate
|
||||
__getattribute__ = object.__getattribute__
|
||||
|
||||
def __del__(self):
|
||||
key = __getattribute__(self, '_local__key')
|
||||
|
||||
try:
|
||||
threads = list(threading_enumerate())
|
||||
except:
|
||||
# if enumerate fails, as it seems to do during
|
||||
# shutdown, we'll skip cleanup under the assumption
|
||||
# that there is nothing to clean up
|
||||
return
|
||||
|
||||
for thread in threads:
|
||||
try:
|
||||
__dict__ = thread.__dict__
|
||||
except AttributeError:
|
||||
# Thread is dying, rest in peace
|
||||
continue
|
||||
|
||||
if key in __dict__:
|
||||
try:
|
||||
del __dict__[key]
|
||||
except KeyError:
|
||||
pass # didn't have anything in this thread
|
||||
|
||||
return __del__
|
||||
__del__ = __del__()
|
||||
|
||||
from threading import currentThread, enumerate, RLock
|
538
deps/cherrypy/_cptools.py
vendored
Normal file
538
deps/cherrypy/_cptools.py
vendored
Normal file
@@ -0,0 +1,538 @@
|
||||
"""CherryPy tools. A "tool" is any helper, adapted to CP.
|
||||
|
||||
Tools are usually designed to be used in a variety of ways (although some
|
||||
may only offer one if they choose):
|
||||
|
||||
Library calls
|
||||
All tools are callables that can be used wherever needed.
|
||||
The arguments are straightforward and should be detailed within the
|
||||
docstring.
|
||||
|
||||
Function decorators
|
||||
All tools, when called, may be used as decorators which configure
|
||||
individual CherryPy page handlers (methods on the CherryPy tree).
|
||||
That is, "@tools.anytool()" should "turn on" the tool via the
|
||||
decorated function's _cp_config attribute.
|
||||
|
||||
CherryPy config
|
||||
If a tool exposes a "_setup" callable, it will be called
|
||||
once per Request (if the feature is "turned on" via config).
|
||||
|
||||
Tools may be implemented as any object with a namespace. The builtins
|
||||
are generally either modules or instances of the tools.Tool class.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._helper import expose
|
||||
|
||||
|
||||
def _getargs(func):
|
||||
"""Return the names of all static arguments to the given function."""
|
||||
# Use this instead of importing inspect for less mem overhead.
|
||||
import types
|
||||
if sys.version_info >= (3, 0):
|
||||
if isinstance(func, types.MethodType):
|
||||
func = func.__func__
|
||||
co = func.__code__
|
||||
else:
|
||||
if isinstance(func, types.MethodType):
|
||||
func = func.im_func
|
||||
co = func.func_code
|
||||
return co.co_varnames[:co.co_argcount]
|
||||
|
||||
|
||||
_attr_error = (
|
||||
"CherryPy Tools cannot be turned on directly. Instead, turn them "
|
||||
"on via config, or use them as decorators on your page handlers."
|
||||
)
|
||||
|
||||
|
||||
class Tool(object):
|
||||
|
||||
"""A registered function for use with CherryPy request-processing hooks.
|
||||
|
||||
help(tool.callable) should give you more information about this Tool.
|
||||
"""
|
||||
|
||||
namespace = "tools"
|
||||
|
||||
def __init__(self, point, callable, name=None, priority=50):
|
||||
self._point = point
|
||||
self.callable = callable
|
||||
self._name = name
|
||||
self._priority = priority
|
||||
self.__doc__ = self.callable.__doc__
|
||||
self._setargs()
|
||||
|
||||
def _get_on(self):
|
||||
raise AttributeError(_attr_error)
|
||||
|
||||
def _set_on(self, value):
|
||||
raise AttributeError(_attr_error)
|
||||
on = property(_get_on, _set_on)
|
||||
|
||||
def _setargs(self):
|
||||
"""Copy func parameter names to obj attributes."""
|
||||
try:
|
||||
for arg in _getargs(self.callable):
|
||||
setattr(self, arg, None)
|
||||
except (TypeError, AttributeError):
|
||||
if hasattr(self.callable, "__call__"):
|
||||
for arg in _getargs(self.callable.__call__):
|
||||
setattr(self, arg, None)
|
||||
# IronPython 1.0 raises NotImplementedError because
|
||||
# inspect.getargspec tries to access Python bytecode
|
||||
# in co_code attribute.
|
||||
except NotImplementedError:
|
||||
pass
|
||||
# IronPython 1B1 may raise IndexError in some cases,
|
||||
# but if we trap it here it doesn't prevent CP from working.
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def _merged_args(self, d=None):
|
||||
"""Return a dict of configuration entries for this Tool."""
|
||||
if d:
|
||||
conf = d.copy()
|
||||
else:
|
||||
conf = {}
|
||||
|
||||
tm = cherrypy.serving.request.toolmaps[self.namespace]
|
||||
if self._name in tm:
|
||||
conf.update(tm[self._name])
|
||||
|
||||
if "on" in conf:
|
||||
del conf["on"]
|
||||
|
||||
return conf
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Compile-time decorator (turn on the tool in config).
|
||||
|
||||
For example::
|
||||
|
||||
@expose
|
||||
@tools.proxy()
|
||||
def whats_my_base(self):
|
||||
return cherrypy.request.base
|
||||
"""
|
||||
if args:
|
||||
raise TypeError("The %r Tool does not accept positional "
|
||||
"arguments; you must use keyword arguments."
|
||||
% self._name)
|
||||
|
||||
def tool_decorator(f):
|
||||
if not hasattr(f, "_cp_config"):
|
||||
f._cp_config = {}
|
||||
subspace = self.namespace + "." + self._name + "."
|
||||
f._cp_config[subspace + "on"] = True
|
||||
for k, v in kwargs.items():
|
||||
f._cp_config[subspace + k] = v
|
||||
return f
|
||||
return tool_decorator
|
||||
|
||||
def _setup(self):
|
||||
"""Hook this tool into cherrypy.request.
|
||||
|
||||
The standard CherryPy request object will automatically call this
|
||||
method when the tool is "turned on" in config.
|
||||
"""
|
||||
conf = self._merged_args()
|
||||
p = conf.pop("priority", None)
|
||||
if p is None:
|
||||
p = getattr(self.callable, "priority", self._priority)
|
||||
cherrypy.serving.request.hooks.attach(self._point, self.callable,
|
||||
priority=p, **conf)
|
||||
|
||||
|
||||
class HandlerTool(Tool):
|
||||
|
||||
"""Tool which is called 'before main', that may skip normal handlers.
|
||||
|
||||
If the tool successfully handles the request (by setting response.body),
|
||||
if should return True. This will cause CherryPy to skip any 'normal' page
|
||||
handler. If the tool did not handle the request, it should return False
|
||||
to tell CherryPy to continue on and call the normal page handler. If the
|
||||
tool is declared AS a page handler (see the 'handler' method), returning
|
||||
False will raise NotFound.
|
||||
"""
|
||||
|
||||
def __init__(self, callable, name=None):
|
||||
Tool.__init__(self, 'before_handler', callable, name)
|
||||
|
||||
def handler(self, *args, **kwargs):
|
||||
"""Use this tool as a CherryPy page handler.
|
||||
|
||||
For example::
|
||||
|
||||
class Root:
|
||||
nav = tools.staticdir.handler(section="/nav", dir="nav",
|
||||
root=absDir)
|
||||
"""
|
||||
@expose
|
||||
def handle_func(*a, **kw):
|
||||
handled = self.callable(*args, **self._merged_args(kwargs))
|
||||
if not handled:
|
||||
raise cherrypy.NotFound()
|
||||
return cherrypy.serving.response.body
|
||||
return handle_func
|
||||
|
||||
def _wrapper(self, **kwargs):
|
||||
if self.callable(**kwargs):
|
||||
cherrypy.serving.request.handler = None
|
||||
|
||||
def _setup(self):
|
||||
"""Hook this tool into cherrypy.request.
|
||||
|
||||
The standard CherryPy request object will automatically call this
|
||||
method when the tool is "turned on" in config.
|
||||
"""
|
||||
conf = self._merged_args()
|
||||
p = conf.pop("priority", None)
|
||||
if p is None:
|
||||
p = getattr(self.callable, "priority", self._priority)
|
||||
cherrypy.serving.request.hooks.attach(self._point, self._wrapper,
|
||||
priority=p, **conf)
|
||||
|
||||
|
||||
class HandlerWrapperTool(Tool):
|
||||
|
||||
"""Tool which wraps request.handler in a provided wrapper function.
|
||||
|
||||
The 'newhandler' arg must be a handler wrapper function that takes a
|
||||
'next_handler' argument, plus ``*args`` and ``**kwargs``. Like all
|
||||
page handler
|
||||
functions, it must return an iterable for use as cherrypy.response.body.
|
||||
|
||||
For example, to allow your 'inner' page handlers to return dicts
|
||||
which then get interpolated into a template::
|
||||
|
||||
def interpolator(next_handler, *args, **kwargs):
|
||||
filename = cherrypy.request.config.get('template')
|
||||
cherrypy.response.template = env.get_template(filename)
|
||||
response_dict = next_handler(*args, **kwargs)
|
||||
return cherrypy.response.template.render(**response_dict)
|
||||
cherrypy.tools.jinja = HandlerWrapperTool(interpolator)
|
||||
"""
|
||||
|
||||
def __init__(self, newhandler, point='before_handler', name=None,
|
||||
priority=50):
|
||||
self.newhandler = newhandler
|
||||
self._point = point
|
||||
self._name = name
|
||||
self._priority = priority
|
||||
|
||||
def callable(self, *args, **kwargs):
|
||||
innerfunc = cherrypy.serving.request.handler
|
||||
|
||||
def wrap(*args, **kwargs):
|
||||
return self.newhandler(innerfunc, *args, **kwargs)
|
||||
cherrypy.serving.request.handler = wrap
|
||||
|
||||
|
||||
class ErrorTool(Tool):
|
||||
|
||||
"""Tool which is used to replace the default request.error_response."""
|
||||
|
||||
def __init__(self, callable, name=None):
|
||||
Tool.__init__(self, None, callable, name)
|
||||
|
||||
def _wrapper(self):
|
||||
self.callable(**self._merged_args())
|
||||
|
||||
def _setup(self):
|
||||
"""Hook this tool into cherrypy.request.
|
||||
|
||||
The standard CherryPy request object will automatically call this
|
||||
method when the tool is "turned on" in config.
|
||||
"""
|
||||
cherrypy.serving.request.error_response = self._wrapper
|
||||
|
||||
|
||||
# Builtin tools #
|
||||
|
||||
from cherrypy.lib import cptools, encoding, auth, static, jsontools
|
||||
from cherrypy.lib import sessions as _sessions, xmlrpcutil as _xmlrpc
|
||||
from cherrypy.lib import caching as _caching
|
||||
from cherrypy.lib import auth_basic, auth_digest
|
||||
|
||||
|
||||
class SessionTool(Tool):
|
||||
|
||||
"""Session Tool for CherryPy.
|
||||
|
||||
sessions.locking
|
||||
When 'implicit' (the default), the session will be locked for you,
|
||||
just before running the page handler.
|
||||
|
||||
When 'early', the session will be locked before reading the request
|
||||
body. This is off by default for safety reasons; for example,
|
||||
a large upload would block the session, denying an AJAX
|
||||
progress meter
|
||||
(`issue <https://github.com/cherrypy/cherrypy/issues/630>`_).
|
||||
|
||||
When 'explicit' (or any other value), you need to call
|
||||
cherrypy.session.acquire_lock() yourself before using
|
||||
session data.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# _sessions.init must be bound after headers are read
|
||||
Tool.__init__(self, 'before_request_body', _sessions.init)
|
||||
|
||||
def _lock_session(self):
|
||||
cherrypy.serving.session.acquire_lock()
|
||||
|
||||
def _setup(self):
|
||||
"""Hook this tool into cherrypy.request.
|
||||
|
||||
The standard CherryPy request object will automatically call this
|
||||
method when the tool is "turned on" in config.
|
||||
"""
|
||||
hooks = cherrypy.serving.request.hooks
|
||||
|
||||
conf = self._merged_args()
|
||||
|
||||
p = conf.pop("priority", None)
|
||||
if p is None:
|
||||
p = getattr(self.callable, "priority", self._priority)
|
||||
|
||||
hooks.attach(self._point, self.callable, priority=p, **conf)
|
||||
|
||||
locking = conf.pop('locking', 'implicit')
|
||||
if locking == 'implicit':
|
||||
hooks.attach('before_handler', self._lock_session)
|
||||
elif locking == 'early':
|
||||
# Lock before the request body (but after _sessions.init runs!)
|
||||
hooks.attach('before_request_body', self._lock_session,
|
||||
priority=60)
|
||||
else:
|
||||
# Don't lock
|
||||
pass
|
||||
|
||||
hooks.attach('before_finalize', _sessions.save)
|
||||
hooks.attach('on_end_request', _sessions.close)
|
||||
|
||||
def regenerate(self):
|
||||
"""Drop the current session and make a new one (with a new id)."""
|
||||
sess = cherrypy.serving.session
|
||||
sess.regenerate()
|
||||
|
||||
# Grab cookie-relevant tool args
|
||||
conf = dict([(k, v) for k, v in self._merged_args().items()
|
||||
if k in ('path', 'path_header', 'name', 'timeout',
|
||||
'domain', 'secure')])
|
||||
_sessions.set_response_cookie(**conf)
|
||||
|
||||
|
||||
class XMLRPCController(object):
|
||||
|
||||
"""A Controller (page handler collection) for XML-RPC.
|
||||
|
||||
To use it, have your controllers subclass this base class (it will
|
||||
turn on the tool for you).
|
||||
|
||||
You can also supply the following optional config entries::
|
||||
|
||||
tools.xmlrpc.encoding: 'utf-8'
|
||||
tools.xmlrpc.allow_none: 0
|
||||
|
||||
XML-RPC is a rather discontinuous layer over HTTP; dispatching to the
|
||||
appropriate handler must first be performed according to the URL, and
|
||||
then a second dispatch step must take place according to the RPC method
|
||||
specified in the request body. It also allows a superfluous "/RPC2"
|
||||
prefix in the URL, supplies its own handler args in the body, and
|
||||
requires a 200 OK "Fault" response instead of 404 when the desired
|
||||
method is not found.
|
||||
|
||||
Therefore, XML-RPC cannot be implemented for CherryPy via a Tool alone.
|
||||
This Controller acts as the dispatch target for the first half (based
|
||||
on the URL); it then reads the RPC method from the request body and
|
||||
does its own second dispatch step based on that method. It also reads
|
||||
body params, and returns a Fault on error.
|
||||
|
||||
The XMLRPCDispatcher strips any /RPC2 prefix; if you aren't using /RPC2
|
||||
in your URL's, you can safely skip turning on the XMLRPCDispatcher.
|
||||
Otherwise, you need to use declare it in config::
|
||||
|
||||
request.dispatch: cherrypy.dispatch.XMLRPCDispatcher()
|
||||
"""
|
||||
|
||||
# Note we're hard-coding this into the 'tools' namespace. We could do
|
||||
# a huge amount of work to make it relocatable, but the only reason why
|
||||
# would be if someone actually disabled the default_toolbox. Meh.
|
||||
_cp_config = {'tools.xmlrpc.on': True}
|
||||
|
||||
@expose
|
||||
def default(self, *vpath, **params):
|
||||
rpcparams, rpcmethod = _xmlrpc.process_body()
|
||||
|
||||
subhandler = self
|
||||
for attr in str(rpcmethod).split('.'):
|
||||
subhandler = getattr(subhandler, attr, None)
|
||||
|
||||
if subhandler and getattr(subhandler, "exposed", False):
|
||||
body = subhandler(*(vpath + rpcparams), **params)
|
||||
|
||||
else:
|
||||
# https://github.com/cherrypy/cherrypy/issues/533
|
||||
# if a method is not found, an xmlrpclib.Fault should be returned
|
||||
# raising an exception here will do that; see
|
||||
# cherrypy.lib.xmlrpcutil.on_error
|
||||
raise Exception('method "%s" is not supported' % attr)
|
||||
|
||||
conf = cherrypy.serving.request.toolmaps['tools'].get("xmlrpc", {})
|
||||
_xmlrpc.respond(body,
|
||||
conf.get('encoding', 'utf-8'),
|
||||
conf.get('allow_none', 0))
|
||||
return cherrypy.serving.response.body
|
||||
|
||||
|
||||
class SessionAuthTool(HandlerTool):
|
||||
|
||||
def _setargs(self):
|
||||
for name in dir(cptools.SessionAuth):
|
||||
if not name.startswith("__"):
|
||||
setattr(self, name, None)
|
||||
|
||||
|
||||
class CachingTool(Tool):
|
||||
|
||||
"""Caching Tool for CherryPy."""
|
||||
|
||||
def _wrapper(self, **kwargs):
|
||||
request = cherrypy.serving.request
|
||||
if _caching.get(**kwargs):
|
||||
request.handler = None
|
||||
else:
|
||||
if request.cacheable:
|
||||
# Note the devious technique here of adding hooks on the fly
|
||||
request.hooks.attach('before_finalize', _caching.tee_output,
|
||||
priority=90)
|
||||
_wrapper.priority = 20
|
||||
|
||||
def _setup(self):
|
||||
"""Hook caching into cherrypy.request."""
|
||||
conf = self._merged_args()
|
||||
|
||||
p = conf.pop("priority", None)
|
||||
cherrypy.serving.request.hooks.attach('before_handler', self._wrapper,
|
||||
priority=p, **conf)
|
||||
|
||||
|
||||
class Toolbox(object):
|
||||
|
||||
"""A collection of Tools.
|
||||
|
||||
This object also functions as a config namespace handler for itself.
|
||||
Custom toolboxes should be added to each Application's toolboxes dict.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace):
|
||||
self.namespace = namespace
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# If the Tool._name is None, supply it from the attribute name.
|
||||
if isinstance(value, Tool):
|
||||
if value._name is None:
|
||||
value._name = name
|
||||
value.namespace = self.namespace
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
def __enter__(self):
|
||||
"""Populate request.toolmaps from tools specified in config."""
|
||||
cherrypy.serving.request.toolmaps[self.namespace] = map = {}
|
||||
|
||||
def populate(k, v):
|
||||
toolname, arg = k.split(".", 1)
|
||||
bucket = map.setdefault(toolname, {})
|
||||
bucket[arg] = v
|
||||
return populate
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Run tool._setup() for each tool in our toolmap."""
|
||||
map = cherrypy.serving.request.toolmaps.get(self.namespace)
|
||||
if map:
|
||||
for name, settings in map.items():
|
||||
if settings.get("on", False):
|
||||
tool = getattr(self, name)
|
||||
tool._setup()
|
||||
|
||||
def register(self, point, **kwargs):
|
||||
"""Return a decorator which registers the function at the given hook point."""
|
||||
def decorator(func):
|
||||
setattr(self, kwargs.get('name', func.__name__), Tool(point, func, **kwargs))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
class DeprecatedTool(Tool):
|
||||
|
||||
_name = None
|
||||
warnmsg = "This Tool is deprecated."
|
||||
|
||||
def __init__(self, point, warnmsg=None):
|
||||
self.point = point
|
||||
if warnmsg is not None:
|
||||
self.warnmsg = warnmsg
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
warnings.warn(self.warnmsg)
|
||||
|
||||
def tool_decorator(f):
|
||||
return f
|
||||
return tool_decorator
|
||||
|
||||
def _setup(self):
|
||||
warnings.warn(self.warnmsg)
|
||||
|
||||
|
||||
default_toolbox = _d = Toolbox("tools")
|
||||
_d.session_auth = SessionAuthTool(cptools.session_auth)
|
||||
_d.allow = Tool('on_start_resource', cptools.allow)
|
||||
_d.proxy = Tool('before_request_body', cptools.proxy, priority=30)
|
||||
_d.response_headers = Tool('on_start_resource', cptools.response_headers)
|
||||
_d.log_tracebacks = Tool('before_error_response', cptools.log_traceback)
|
||||
_d.log_headers = Tool('before_error_response', cptools.log_request_headers)
|
||||
_d.log_hooks = Tool('on_end_request', cptools.log_hooks, priority=100)
|
||||
_d.err_redirect = ErrorTool(cptools.redirect)
|
||||
_d.etags = Tool('before_finalize', cptools.validate_etags, priority=75)
|
||||
_d.decode = Tool('before_request_body', encoding.decode)
|
||||
# the order of encoding, gzip, caching is important
|
||||
_d.encode = Tool('before_handler', encoding.ResponseEncoder, priority=70)
|
||||
_d.gzip = Tool('before_finalize', encoding.gzip, priority=80)
|
||||
_d.staticdir = HandlerTool(static.staticdir)
|
||||
_d.staticfile = HandlerTool(static.staticfile)
|
||||
_d.sessions = SessionTool()
|
||||
_d.xmlrpc = ErrorTool(_xmlrpc.on_error)
|
||||
_d.caching = CachingTool('before_handler', _caching.get, 'caching')
|
||||
_d.expires = Tool('before_finalize', _caching.expires)
|
||||
_d.tidy = DeprecatedTool(
|
||||
'before_finalize',
|
||||
"The tidy tool has been removed from the standard distribution of "
|
||||
"CherryPy. The most recent version can be found at "
|
||||
"http://tools.cherrypy.org/browser.")
|
||||
_d.nsgmls = DeprecatedTool(
|
||||
'before_finalize',
|
||||
"The nsgmls tool has been removed from the standard distribution of "
|
||||
"CherryPy. The most recent version can be found at "
|
||||
"http://tools.cherrypy.org/browser.")
|
||||
_d.ignore_headers = Tool('before_request_body', cptools.ignore_headers)
|
||||
_d.referer = Tool('before_request_body', cptools.referer)
|
||||
_d.basic_auth = Tool('on_start_resource', auth.basic_auth)
|
||||
_d.digest_auth = Tool('on_start_resource', auth.digest_auth)
|
||||
_d.trailing_slash = Tool('before_handler', cptools.trailing_slash, priority=60)
|
||||
_d.flatten = Tool('before_finalize', cptools.flatten)
|
||||
_d.accept = Tool('on_start_resource', cptools.accept)
|
||||
_d.redirect = Tool('on_start_resource', cptools.redirect)
|
||||
_d.autovary = Tool('on_start_resource', cptools.autovary, priority=0)
|
||||
_d.json_in = Tool('before_request_body', jsontools.json_in, priority=30)
|
||||
_d.json_out = Tool('before_handler', jsontools.json_out, priority=30)
|
||||
_d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1)
|
||||
_d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1)
|
||||
_d.params = Tool('before_handler', cptools.convert_params)
|
||||
|
||||
del _d, cptools, encoding, auth, static
|
287
deps/cherrypy/_cptree.py
vendored
Normal file
287
deps/cherrypy/_cptree.py
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
"""CherryPy Application and Tree objects."""
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import ntou
|
||||
from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
|
||||
class Application(object):
|
||||
|
||||
"""A CherryPy Application.
|
||||
|
||||
Servers and gateways should not instantiate Request objects directly.
|
||||
Instead, they should ask an Application object for a request object.
|
||||
|
||||
An instance of this class may also be used as a WSGI callable
|
||||
(WSGI application object) for itself.
|
||||
"""
|
||||
|
||||
root = None
|
||||
"""The top-most container of page handlers for this app. Handlers should
|
||||
be arranged in a hierarchy of attributes, matching the expected URI
|
||||
hierarchy; the default dispatcher then searches this hierarchy for a
|
||||
matching handler. When using a dispatcher other than the default,
|
||||
this value may be None."""
|
||||
|
||||
config = {}
|
||||
"""A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict
|
||||
of {key: value} pairs."""
|
||||
|
||||
namespaces = _cpconfig.NamespaceSet()
|
||||
toolboxes = {'tools': cherrypy.tools}
|
||||
|
||||
log = None
|
||||
"""A LogManager instance. See _cplogging."""
|
||||
|
||||
wsgiapp = None
|
||||
"""A CPWSGIApp instance. See _cpwsgi."""
|
||||
|
||||
request_class = _cprequest.Request
|
||||
response_class = _cprequest.Response
|
||||
|
||||
relative_urls = False
|
||||
|
||||
def __init__(self, root, script_name="", config=None):
|
||||
self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root)
|
||||
self.root = root
|
||||
self.script_name = script_name
|
||||
self.wsgiapp = _cpwsgi.CPWSGIApp(self)
|
||||
|
||||
self.namespaces = self.namespaces.copy()
|
||||
self.namespaces["log"] = lambda k, v: setattr(self.log, k, v)
|
||||
self.namespaces["wsgi"] = self.wsgiapp.namespace_handler
|
||||
|
||||
self.config = self.__class__.config.copy()
|
||||
if config:
|
||||
self.merge(config)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__,
|
||||
self.root, self.script_name)
|
||||
|
||||
script_name_doc = """The URI "mount point" for this app. A mount point
|
||||
is that portion of the URI which is constant for all URIs that are
|
||||
serviced by this application; it does not include scheme, host, or proxy
|
||||
("virtual host") portions of the URI.
|
||||
|
||||
For example, if script_name is "/my/cool/app", then the URL
|
||||
"http://www.example.com/my/cool/app/page1" might be handled by a
|
||||
"page1" method on the root object.
|
||||
|
||||
The value of script_name MUST NOT end in a slash. If the script_name
|
||||
refers to the root of the URI, it MUST be an empty string (not "/").
|
||||
|
||||
If script_name is explicitly set to None, then the script_name will be
|
||||
provided for each call from request.wsgi_environ['SCRIPT_NAME'].
|
||||
"""
|
||||
|
||||
def _get_script_name(self):
|
||||
if self._script_name is not None:
|
||||
return self._script_name
|
||||
|
||||
# A `_script_name` with a value of None signals that the script name
|
||||
# should be pulled from WSGI environ.
|
||||
return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/")
|
||||
|
||||
def _set_script_name(self, value):
|
||||
if value:
|
||||
value = value.rstrip("/")
|
||||
self._script_name = value
|
||||
script_name = property(fget=_get_script_name, fset=_set_script_name,
|
||||
doc=script_name_doc)
|
||||
|
||||
def merge(self, config):
|
||||
"""Merge the given config into self.config."""
|
||||
_cpconfig.merge(self.config, config)
|
||||
|
||||
# Handle namespaces specified in config.
|
||||
self.namespaces(self.config.get("/", {}))
|
||||
|
||||
def find_config(self, path, key, default=None):
|
||||
"""Return the most-specific value for key along path, or default."""
|
||||
trail = path or "/"
|
||||
while trail:
|
||||
nodeconf = self.config.get(trail, {})
|
||||
|
||||
if key in nodeconf:
|
||||
return nodeconf[key]
|
||||
|
||||
lastslash = trail.rfind("/")
|
||||
if lastslash == -1:
|
||||
break
|
||||
elif lastslash == 0 and trail != "/":
|
||||
trail = "/"
|
||||
else:
|
||||
trail = trail[:lastslash]
|
||||
|
||||
return default
|
||||
|
||||
def get_serving(self, local, remote, scheme, sproto):
|
||||
"""Create and return a Request and Response object."""
|
||||
req = self.request_class(local, remote, scheme, sproto)
|
||||
req.app = self
|
||||
|
||||
for name, toolbox in self.toolboxes.items():
|
||||
req.namespaces[name] = toolbox
|
||||
|
||||
resp = self.response_class()
|
||||
cherrypy.serving.load(req, resp)
|
||||
cherrypy.engine.publish('acquire_thread')
|
||||
cherrypy.engine.publish('before_request')
|
||||
|
||||
return req, resp
|
||||
|
||||
def release_serving(self):
|
||||
"""Release the current serving (request and response)."""
|
||||
req = cherrypy.serving.request
|
||||
|
||||
cherrypy.engine.publish('after_request')
|
||||
|
||||
try:
|
||||
req.close()
|
||||
except:
|
||||
cherrypy.log(traceback=True, severity=40)
|
||||
|
||||
cherrypy.serving.clear()
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return self.wsgiapp(environ, start_response)
|
||||
|
||||
|
||||
class Tree(object):
|
||||
|
||||
"""A registry of CherryPy applications, mounted at diverse points.
|
||||
|
||||
An instance of this class may also be used as a WSGI callable
|
||||
(WSGI application object), in which case it dispatches to all
|
||||
mounted apps.
|
||||
"""
|
||||
|
||||
apps = {}
|
||||
"""
|
||||
A dict of the form {script name: application}, where "script name"
|
||||
is a string declaring the URI mount point (no trailing slash), and
|
||||
"application" is an instance of cherrypy.Application (or an arbitrary
|
||||
WSGI callable if you happen to be using a WSGI server)."""
|
||||
|
||||
def __init__(self):
|
||||
self.apps = {}
|
||||
|
||||
def mount(self, root, script_name="", config=None):
|
||||
"""Mount a new app from a root object, script_name, and config.
|
||||
|
||||
root
|
||||
An instance of a "controller class" (a collection of page
|
||||
handler methods) which represents the root of the application.
|
||||
This may also be an Application instance, or None if using
|
||||
a dispatcher other than the default.
|
||||
|
||||
script_name
|
||||
A string containing the "mount point" of the application.
|
||||
This should start with a slash, and be the path portion of the
|
||||
URL at which to mount the given root. For example, if root.index()
|
||||
will handle requests to "http://www.example.com:8080/dept/app1/",
|
||||
then the script_name argument would be "/dept/app1".
|
||||
|
||||
It MUST NOT end in a slash. If the script_name refers to the
|
||||
root of the URI, it MUST be an empty string (not "/").
|
||||
|
||||
config
|
||||
A file or dict containing application config.
|
||||
"""
|
||||
if script_name is None:
|
||||
raise TypeError(
|
||||
"The 'script_name' argument may not be None. Application "
|
||||
"objects may, however, possess a script_name of None (in "
|
||||
"order to inpect the WSGI environ for SCRIPT_NAME upon each "
|
||||
"request). You cannot mount such Applications on this Tree; "
|
||||
"you must pass them to a WSGI server interface directly.")
|
||||
|
||||
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
||||
script_name = script_name.rstrip("/")
|
||||
|
||||
if isinstance(root, Application):
|
||||
app = root
|
||||
if script_name != "" and script_name != app.script_name:
|
||||
raise ValueError(
|
||||
"Cannot specify a different script name and pass an "
|
||||
"Application instance to cherrypy.mount")
|
||||
script_name = app.script_name
|
||||
else:
|
||||
app = Application(root, script_name)
|
||||
|
||||
# If mounted at "", add favicon.ico
|
||||
if (script_name == "" and root is not None
|
||||
and not hasattr(root, "favicon_ico")):
|
||||
favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
|
||||
"favicon.ico")
|
||||
root.favicon_ico = tools.staticfile.handler(favicon)
|
||||
|
||||
if config:
|
||||
app.merge(config)
|
||||
|
||||
self.apps[script_name] = app
|
||||
|
||||
return app
|
||||
|
||||
def graft(self, wsgi_callable, script_name=""):
|
||||
"""Mount a wsgi callable at the given script_name."""
|
||||
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
||||
script_name = script_name.rstrip("/")
|
||||
self.apps[script_name] = wsgi_callable
|
||||
|
||||
def script_name(self, path=None):
|
||||
"""The script_name of the app at the given path, or None.
|
||||
|
||||
If path is None, cherrypy.request is used.
|
||||
"""
|
||||
if path is None:
|
||||
try:
|
||||
request = cherrypy.serving.request
|
||||
path = httputil.urljoin(request.script_name,
|
||||
request.path_info)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
while True:
|
||||
if path in self.apps:
|
||||
return path
|
||||
|
||||
if path == "":
|
||||
return None
|
||||
|
||||
# Move one node up the tree and try again.
|
||||
path = path[:path.rfind("/")]
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# If you're calling this, then you're probably setting SCRIPT_NAME
|
||||
# to '' (some WSGI servers always set SCRIPT_NAME to '').
|
||||
# Try to look up the app using the full path.
|
||||
env1x = environ
|
||||
if six.PY2 and environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
|
||||
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
|
||||
env1x.get('PATH_INFO', ''))
|
||||
sn = self.script_name(path or "/")
|
||||
if sn is None:
|
||||
start_response('404 Not Found', [])
|
||||
return []
|
||||
|
||||
app = self.apps[sn]
|
||||
|
||||
# Correct the SCRIPT_NAME and PATH_INFO environ entries.
|
||||
environ = environ.copy()
|
||||
if six.PY2 and environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
# Python 2/WSGI u.0: all strings MUST be of type unicode
|
||||
enc = environ[ntou('wsgi.url_encoding')]
|
||||
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
|
||||
environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
|
||||
else:
|
||||
environ['SCRIPT_NAME'] = sn
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
||||
return app(environ, start_response)
|
467
deps/cherrypy/_cpwsgi.py
vendored
Normal file
467
deps/cherrypy/_cpwsgi.py
vendored
Normal file
@@ -0,0 +1,467 @@
|
||||
"""WSGI interface (see PEP 333 and 3333).
|
||||
|
||||
Note that WSGI environ keys and values are 'native strings'; that is,
|
||||
whatever the type of "" is. For Python 2, that's a byte string; for Python 3,
|
||||
it's a unicode string. But PEP 3333 says: "even if Python's str type is
|
||||
actually Unicode "under the hood", the content of native strings must
|
||||
still be translatable to bytes via the Latin-1 encoding!"
|
||||
"""
|
||||
|
||||
import sys as _sys
|
||||
import io
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy as _cherrypy
|
||||
from cherrypy._cpcompat import ntob, ntou
|
||||
from cherrypy import _cperror
|
||||
from cherrypy.lib import httputil
|
||||
from cherrypy.lib import is_closable_iterator
|
||||
|
||||
def downgrade_wsgi_ux_to_1x(environ):
|
||||
"""Return a new environ dict for WSGI 1.x from the given WSGI u.x environ.
|
||||
"""
|
||||
env1x = {}
|
||||
|
||||
url_encoding = environ[ntou('wsgi.url_encoding')]
|
||||
for k, v in list(environ.items()):
|
||||
if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
|
||||
v = v.encode(url_encoding)
|
||||
elif isinstance(v, six.text_type):
|
||||
v = v.encode('ISO-8859-1')
|
||||
env1x[k.encode('ISO-8859-1')] = v
|
||||
|
||||
return env1x
|
||||
|
||||
|
||||
class VirtualHost(object):
|
||||
|
||||
"""Select a different WSGI application based on the Host header.
|
||||
|
||||
This can be useful when running multiple sites within one CP server.
|
||||
It allows several domains to point to different applications. For example::
|
||||
|
||||
root = Root()
|
||||
RootApp = cherrypy.Application(root)
|
||||
Domain2App = cherrypy.Application(root)
|
||||
SecureApp = cherrypy.Application(Secure())
|
||||
|
||||
vhost = cherrypy._cpwsgi.VirtualHost(
|
||||
RootApp,
|
||||
domains={
|
||||
'www.domain2.example': Domain2App,
|
||||
'www.domain2.example:443': SecureApp,
|
||||
},
|
||||
)
|
||||
|
||||
cherrypy.tree.graft(vhost)
|
||||
"""
|
||||
default = None
|
||||
"""Required. The default WSGI application."""
|
||||
|
||||
use_x_forwarded_host = True
|
||||
"""If True (the default), any "X-Forwarded-Host"
|
||||
request header will be used instead of the "Host" header. This
|
||||
is commonly added by HTTP servers (such as Apache) when proxying."""
|
||||
|
||||
domains = {}
|
||||
"""A dict of {host header value: application} pairs.
|
||||
The incoming "Host" request header is looked up in this dict,
|
||||
and, if a match is found, the corresponding WSGI application
|
||||
will be called instead of the default. Note that you often need
|
||||
separate entries for "example.com" and "www.example.com".
|
||||
In addition, "Host" headers may contain the port number.
|
||||
"""
|
||||
|
||||
def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
||||
self.default = default
|
||||
self.domains = domains or {}
|
||||
self.use_x_forwarded_host = use_x_forwarded_host
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
domain = environ.get('HTTP_HOST', '')
|
||||
if self.use_x_forwarded_host:
|
||||
domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
|
||||
|
||||
nextapp = self.domains.get(domain)
|
||||
if nextapp is None:
|
||||
nextapp = self.default
|
||||
return nextapp(environ, start_response)
|
||||
|
||||
|
||||
class InternalRedirector(object):
|
||||
|
||||
"""WSGI middleware that handles raised cherrypy.InternalRedirect."""
|
||||
|
||||
def __init__(self, nextapp, recursive=False):
|
||||
self.nextapp = nextapp
|
||||
self.recursive = recursive
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
redirections = []
|
||||
while True:
|
||||
environ = environ.copy()
|
||||
try:
|
||||
return self.nextapp(environ, start_response)
|
||||
except _cherrypy.InternalRedirect:
|
||||
ir = _sys.exc_info()[1]
|
||||
sn = environ.get('SCRIPT_NAME', '')
|
||||
path = environ.get('PATH_INFO', '')
|
||||
qs = environ.get('QUERY_STRING', '')
|
||||
|
||||
# Add the *previous* path_info + qs to redirections.
|
||||
old_uri = sn + path
|
||||
if qs:
|
||||
old_uri += "?" + qs
|
||||
redirections.append(old_uri)
|
||||
|
||||
if not self.recursive:
|
||||
# Check to see if the new URI has been redirected to
|
||||
# already
|
||||
new_uri = sn + ir.path
|
||||
if ir.query_string:
|
||||
new_uri += "?" + ir.query_string
|
||||
if new_uri in redirections:
|
||||
ir.request.close()
|
||||
tmpl = (
|
||||
"InternalRedirector visited the same URL twice: %r"
|
||||
)
|
||||
raise RuntimeError(tmpl % new_uri)
|
||||
|
||||
# Munge the environment and try again.
|
||||
environ['REQUEST_METHOD'] = "GET"
|
||||
environ['PATH_INFO'] = ir.path
|
||||
environ['QUERY_STRING'] = ir.query_string
|
||||
environ['wsgi.input'] = io.BytesIO()
|
||||
environ['CONTENT_LENGTH'] = "0"
|
||||
environ['cherrypy.previous_request'] = ir.request
|
||||
|
||||
|
||||
class ExceptionTrapper(object):
|
||||
|
||||
"""WSGI middleware that traps exceptions."""
|
||||
|
||||
def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
|
||||
self.nextapp = nextapp
|
||||
self.throws = throws
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return _TrappedResponse(
|
||||
self.nextapp,
|
||||
environ,
|
||||
start_response,
|
||||
self.throws
|
||||
)
|
||||
|
||||
|
||||
class _TrappedResponse(object):
|
||||
|
||||
response = iter([])
|
||||
|
||||
def __init__(self, nextapp, environ, start_response, throws):
|
||||
self.nextapp = nextapp
|
||||
self.environ = environ
|
||||
self.start_response = start_response
|
||||
self.throws = throws
|
||||
self.started_response = False
|
||||
self.response = self.trap(
|
||||
self.nextapp, self.environ, self.start_response,
|
||||
)
|
||||
self.iter_response = iter(self.response)
|
||||
|
||||
def __iter__(self):
|
||||
self.started_response = True
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.trap(next, self.iter_response)
|
||||
|
||||
# todo: https://pythonhosted.org/six/#six.Iterator
|
||||
if six.PY2:
|
||||
next = __next__
|
||||
|
||||
def close(self):
|
||||
if hasattr(self.response, 'close'):
|
||||
self.response.close()
|
||||
|
||||
def trap(self, func, *args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except self.throws:
|
||||
raise
|
||||
except StopIteration:
|
||||
raise
|
||||
except:
|
||||
tb = _cperror.format_exc()
|
||||
#print('trapped (started %s):' % self.started_response, tb)
|
||||
_cherrypy.log(tb, severity=40)
|
||||
if not _cherrypy.request.show_tracebacks:
|
||||
tb = ""
|
||||
s, h, b = _cperror.bare_error(tb)
|
||||
if six.PY3:
|
||||
# What fun.
|
||||
s = s.decode('ISO-8859-1')
|
||||
h = [
|
||||
(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||
for k, v in h
|
||||
]
|
||||
if self.started_response:
|
||||
# Empty our iterable (so future calls raise StopIteration)
|
||||
self.iter_response = iter([])
|
||||
else:
|
||||
self.iter_response = iter(b)
|
||||
|
||||
try:
|
||||
self.start_response(s, h, _sys.exc_info())
|
||||
except:
|
||||
# "The application must not trap any exceptions raised by
|
||||
# start_response, if it called start_response with exc_info.
|
||||
# Instead, it should allow such exceptions to propagate
|
||||
# back to the server or gateway."
|
||||
# But we still log and call close() to clean up ourselves.
|
||||
_cherrypy.log(traceback=True, severity=40)
|
||||
raise
|
||||
|
||||
if self.started_response:
|
||||
return ntob("").join(b)
|
||||
else:
|
||||
return b
|
||||
|
||||
|
||||
# WSGI-to-CP Adapter #
|
||||
|
||||
|
||||
class AppResponse(object):
|
||||
|
||||
"""WSGI response iterable for CherryPy applications."""
|
||||
|
||||
def __init__(self, environ, start_response, cpapp):
|
||||
self.cpapp = cpapp
|
||||
try:
|
||||
if six.PY2:
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
environ = downgrade_wsgi_ux_to_1x(environ)
|
||||
self.environ = environ
|
||||
self.run()
|
||||
|
||||
r = _cherrypy.serving.response
|
||||
|
||||
outstatus = r.output_status
|
||||
if not isinstance(outstatus, bytes):
|
||||
raise TypeError("response.output_status is not a byte string.")
|
||||
|
||||
outheaders = []
|
||||
for k, v in r.header_list:
|
||||
if not isinstance(k, bytes):
|
||||
tmpl = "response.header_list key %r is not a byte string."
|
||||
raise TypeError(tmpl % k)
|
||||
if not isinstance(v, bytes):
|
||||
tmpl = (
|
||||
"response.header_list value %r is not a byte string."
|
||||
)
|
||||
raise TypeError(tmpl % v)
|
||||
outheaders.append((k, v))
|
||||
|
||||
if six.PY3:
|
||||
# According to PEP 3333, when using Python 3, the response
|
||||
# status and headers must be bytes masquerading as unicode;
|
||||
# that is, they must be of type "str" but are restricted to
|
||||
# code points in the "latin-1" set.
|
||||
outstatus = outstatus.decode('ISO-8859-1')
|
||||
outheaders = [
|
||||
(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||
for k, v in outheaders
|
||||
]
|
||||
|
||||
self.iter_response = iter(r.body)
|
||||
self.write = start_response(outstatus, outheaders)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return next(self.iter_response)
|
||||
|
||||
# todo: https://pythonhosted.org/six/#six.Iterator
|
||||
if six.PY2:
|
||||
next = __next__
|
||||
|
||||
def close(self):
|
||||
"""Close and de-reference the current request and response. (Core)"""
|
||||
streaming = _cherrypy.serving.response.stream
|
||||
self.cpapp.release_serving()
|
||||
|
||||
# We avoid the expense of examining the iterator to see if it's
|
||||
# closable unless we are streaming the response, as that's the
|
||||
# only situation where we are going to have an iterator which
|
||||
# may not have been exhausted yet.
|
||||
if streaming and is_closable_iterator(self.iter_response):
|
||||
iter_close = self.iter_response.close
|
||||
try:
|
||||
iter_close()
|
||||
except Exception:
|
||||
_cherrypy.log(traceback=True, severity=40)
|
||||
|
||||
def run(self):
|
||||
"""Create a Request object using environ."""
|
||||
env = self.environ.get
|
||||
|
||||
local = httputil.Host(
|
||||
'',
|
||||
int(env('SERVER_PORT', 80) or -1),
|
||||
env('SERVER_NAME', ''),
|
||||
)
|
||||
remote = httputil.Host(
|
||||
env('REMOTE_ADDR', ''),
|
||||
int(env('REMOTE_PORT', -1) or -1),
|
||||
env('REMOTE_HOST', ''),
|
||||
)
|
||||
scheme = env('wsgi.url_scheme')
|
||||
sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
|
||||
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
||||
|
||||
# LOGON_USER is served by IIS, and is the name of the
|
||||
# user after having been mapped to a local account.
|
||||
# Both IIS and Apache set REMOTE_USER, when possible.
|
||||
request.login = env('LOGON_USER') or env('REMOTE_USER') or None
|
||||
request.multithread = self.environ['wsgi.multithread']
|
||||
request.multiprocess = self.environ['wsgi.multiprocess']
|
||||
request.wsgi_environ = self.environ
|
||||
request.prev = env('cherrypy.previous_request', None)
|
||||
|
||||
meth = self.environ['REQUEST_METHOD']
|
||||
|
||||
path = httputil.urljoin(
|
||||
self.environ.get('SCRIPT_NAME', ''),
|
||||
self.environ.get('PATH_INFO', ''),
|
||||
)
|
||||
qs = self.environ.get('QUERY_STRING', '')
|
||||
|
||||
path, qs = self.recode_path_qs(path, qs) or (path, qs)
|
||||
|
||||
rproto = self.environ.get('SERVER_PROTOCOL')
|
||||
headers = self.translate_headers(self.environ)
|
||||
rfile = self.environ['wsgi.input']
|
||||
request.run(meth, path, qs, rproto, headers, rfile)
|
||||
|
||||
headerNames = {
|
||||
'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
||||
'CONTENT_LENGTH': 'Content-Length',
|
||||
'CONTENT_TYPE': 'Content-Type',
|
||||
'REMOTE_HOST': 'Remote-Host',
|
||||
'REMOTE_ADDR': 'Remote-Addr',
|
||||
}
|
||||
|
||||
def recode_path_qs(self, path, qs):
|
||||
if not six.PY3:
|
||||
return
|
||||
|
||||
# This isn't perfect; if the given PATH_INFO is in the
|
||||
# wrong encoding, it may fail to match the appropriate config
|
||||
# section URI. But meh.
|
||||
old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
|
||||
new_enc = self.cpapp.find_config(
|
||||
self.environ.get('PATH_INFO', ''),
|
||||
"request.uri_encoding", 'utf-8',
|
||||
)
|
||||
if new_enc.lower() == old_enc.lower():
|
||||
return
|
||||
|
||||
# Even though the path and qs are unicode, the WSGI server
|
||||
# is required by PEP 3333 to coerce them to ISO-8859-1
|
||||
# masquerading as unicode. So we have to encode back to
|
||||
# bytes and then decode again using the "correct" encoding.
|
||||
try:
|
||||
return (
|
||||
path.encode(old_enc).decode(new_enc),
|
||||
qs.encode(old_enc).decode(new_enc),
|
||||
)
|
||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||
# Just pass them through without transcoding and hope.
|
||||
pass
|
||||
|
||||
def translate_headers(self, environ):
|
||||
"""Translate CGI-environ header names to HTTP header names."""
|
||||
for cgiName in environ:
|
||||
# We assume all incoming header keys are uppercase already.
|
||||
if cgiName in self.headerNames:
|
||||
yield self.headerNames[cgiName], environ[cgiName]
|
||||
elif cgiName[:5] == "HTTP_":
|
||||
# Hackish attempt at recovering original header names.
|
||||
translatedHeader = cgiName[5:].replace("_", "-")
|
||||
yield translatedHeader, environ[cgiName]
|
||||
|
||||
|
||||
class CPWSGIApp(object):
|
||||
|
||||
"""A WSGI application object for a CherryPy Application."""
|
||||
|
||||
pipeline = [
|
||||
('ExceptionTrapper', ExceptionTrapper),
|
||||
('InternalRedirector', InternalRedirector),
|
||||
]
|
||||
"""A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
||||
constructor that takes an initial, positional 'nextapp' argument,
|
||||
plus optional keyword arguments, and returns a WSGI application
|
||||
(that takes environ and start_response arguments). The 'name' can
|
||||
be any you choose, and will correspond to keys in self.config."""
|
||||
|
||||
head = None
|
||||
"""Rather than nest all apps in the pipeline on each call, it's only
|
||||
done the first time, and the result is memoized into self.head. Set
|
||||
this to None again if you change self.pipeline after calling self."""
|
||||
|
||||
config = {}
|
||||
"""A dict whose keys match names listed in the pipeline. Each
|
||||
value is a further dict which will be passed to the corresponding
|
||||
named WSGI callable (from the pipeline) as keyword arguments."""
|
||||
|
||||
response_class = AppResponse
|
||||
"""The class to instantiate and return as the next app in the WSGI chain.
|
||||
"""
|
||||
|
||||
def __init__(self, cpapp, pipeline=None):
|
||||
self.cpapp = cpapp
|
||||
self.pipeline = self.pipeline[:]
|
||||
if pipeline:
|
||||
self.pipeline.extend(pipeline)
|
||||
self.config = self.config.copy()
|
||||
|
||||
def tail(self, environ, start_response):
|
||||
"""WSGI application callable for the actual CherryPy application.
|
||||
|
||||
You probably shouldn't call this; call self.__call__ instead,
|
||||
so that any WSGI middleware in self.pipeline can run first.
|
||||
"""
|
||||
return self.response_class(environ, start_response, self.cpapp)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
head = self.head
|
||||
if head is None:
|
||||
# Create and nest the WSGI apps in our pipeline (in reverse order).
|
||||
# Then memoize the result in self.head.
|
||||
head = self.tail
|
||||
for name, callable in self.pipeline[::-1]:
|
||||
conf = self.config.get(name, {})
|
||||
head = callable(head, **conf)
|
||||
self.head = head
|
||||
return head(environ, start_response)
|
||||
|
||||
def namespace_handler(self, k, v):
|
||||
"""Config handler for the 'wsgi' namespace."""
|
||||
if k == "pipeline":
|
||||
# Note this allows multiple 'wsgi.pipeline' config entries
|
||||
# (but each entry will be processed in a 'random' order).
|
||||
# It should also allow developers to set default middleware
|
||||
# in code (passed to self.__init__) that deployers can add to
|
||||
# (but not remove) via config.
|
||||
self.pipeline.extend(v)
|
||||
elif k == "response_class":
|
||||
self.response_class = v
|
||||
else:
|
||||
name, arg = k.split(".", 1)
|
||||
bucket = self.config.setdefault(name, {})
|
||||
bucket[arg] = v
|
70
deps/cherrypy/_cpwsgi_server.py
vendored
Normal file
70
deps/cherrypy/_cpwsgi_server.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
"""WSGI server interface (see PEP 333). This adds some CP-specific bits to
|
||||
the framework-agnostic wsgiserver package.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
|
||||
|
||||
"""Wrapper for wsgiserver.CherryPyWSGIServer.
|
||||
|
||||
wsgiserver has been designed to not reference CherryPy in any way,
|
||||
so that it can be used in other frameworks and applications. Therefore,
|
||||
we wrap it here, so we can set our own mount points from cherrypy.tree
|
||||
and apply some attributes from config -> cherrypy.server -> wsgiserver.
|
||||
"""
|
||||
|
||||
def __init__(self, server_adapter=cherrypy.server):
|
||||
self.server_adapter = server_adapter
|
||||
self.max_request_header_size = (
|
||||
self.server_adapter.max_request_header_size or 0
|
||||
)
|
||||
self.max_request_body_size = (
|
||||
self.server_adapter.max_request_body_size or 0
|
||||
)
|
||||
|
||||
server_name = (self.server_adapter.socket_host or
|
||||
self.server_adapter.socket_file or
|
||||
None)
|
||||
|
||||
self.wsgi_version = self.server_adapter.wsgi_version
|
||||
s = wsgiserver.CherryPyWSGIServer
|
||||
s.__init__(self, server_adapter.bind_addr, cherrypy.tree,
|
||||
self.server_adapter.thread_pool,
|
||||
server_name,
|
||||
max=self.server_adapter.thread_pool_max,
|
||||
request_queue_size=self.server_adapter.socket_queue_size,
|
||||
timeout=self.server_adapter.socket_timeout,
|
||||
shutdown_timeout=self.server_adapter.shutdown_timeout,
|
||||
accepted_queue_size=self.server_adapter.accepted_queue_size,
|
||||
accepted_queue_timeout=self.server_adapter.accepted_queue_timeout,
|
||||
)
|
||||
self.protocol = self.server_adapter.protocol_version
|
||||
self.nodelay = self.server_adapter.nodelay
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
ssl_module = self.server_adapter.ssl_module or 'builtin'
|
||||
else:
|
||||
ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
|
||||
if self.server_adapter.ssl_context:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
self.ssl_adapter.context = self.server_adapter.ssl_context
|
||||
elif self.server_adapter.ssl_certificate:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
|
||||
self.stats['Enabled'] = getattr(
|
||||
self.server_adapter, 'statistics', False)
|
||||
|
||||
def error_log(self, msg="", level=20, traceback=False):
|
||||
cherrypy.engine.log(msg, level, traceback)
|
298
deps/cherrypy/_helper.py
vendored
Normal file
298
deps/cherrypy/_helper.py
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
"""
|
||||
Helper functions for CP apps
|
||||
"""
|
||||
|
||||
import six
|
||||
|
||||
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
|
||||
from cherrypy._cpcompat import text_or_bytes
|
||||
|
||||
import cherrypy
|
||||
|
||||
|
||||
def expose(func=None, alias=None):
|
||||
"""
|
||||
Expose the function or class, optionally providing an alias or set of aliases.
|
||||
"""
|
||||
def expose_(func):
|
||||
func.exposed = True
|
||||
if alias is not None:
|
||||
if isinstance(alias, text_or_bytes):
|
||||
parents[alias.replace(".", "_")] = func
|
||||
else:
|
||||
for a in alias:
|
||||
parents[a.replace(".", "_")] = func
|
||||
return func
|
||||
|
||||
import sys
|
||||
import types
|
||||
decoratable_types = types.FunctionType, types.MethodType, type,
|
||||
if six.PY2:
|
||||
# Old-style classes are type types.ClassType.
|
||||
decoratable_types += types.ClassType,
|
||||
if isinstance(func, decoratable_types):
|
||||
if alias is None:
|
||||
# @expose
|
||||
func.exposed = True
|
||||
return func
|
||||
else:
|
||||
# func = expose(func, alias)
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_(func)
|
||||
elif func is None:
|
||||
if alias is None:
|
||||
# @expose()
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_
|
||||
else:
|
||||
# @expose(alias="alias") or
|
||||
# @expose(alias=["alias1", "alias2"])
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_
|
||||
else:
|
||||
# @expose("alias") or
|
||||
# @expose(["alias1", "alias2"])
|
||||
parents = sys._getframe(1).f_locals
|
||||
alias = func
|
||||
return expose_
|
||||
|
||||
|
||||
def popargs(*args, **kwargs):
|
||||
"""A decorator for _cp_dispatch
|
||||
(cherrypy.dispatch.Dispatcher.dispatch_method_name).
|
||||
|
||||
Optional keyword argument: handler=(Object or Function)
|
||||
|
||||
Provides a _cp_dispatch function that pops off path segments into
|
||||
cherrypy.request.params under the names specified. The dispatch
|
||||
is then forwarded on to the next vpath element.
|
||||
|
||||
Note that any existing (and exposed) member function of the class that
|
||||
popargs is applied to will override that value of the argument. For
|
||||
instance, if you have a method named "list" on the class decorated with
|
||||
popargs, then accessing "/list" will call that function instead of popping
|
||||
it off as the requested parameter. This restriction applies to all
|
||||
_cp_dispatch functions. The only way around this restriction is to create
|
||||
a "blank class" whose only function is to provide _cp_dispatch.
|
||||
|
||||
If there are path elements after the arguments, or more arguments
|
||||
are requested than are available in the vpath, then the 'handler'
|
||||
keyword argument specifies the next object to handle the parameterized
|
||||
request. If handler is not specified or is None, then self is used.
|
||||
If handler is a function rather than an instance, then that function
|
||||
will be called with the args specified and the return value from that
|
||||
function used as the next object INSTEAD of adding the parameters to
|
||||
cherrypy.request.args.
|
||||
|
||||
This decorator may be used in one of two ways:
|
||||
|
||||
As a class decorator:
|
||||
@cherrypy.popargs('year', 'month', 'day')
|
||||
class Blog:
|
||||
def index(self, year=None, month=None, day=None):
|
||||
#Process the parameters here; any url like
|
||||
#/, /2009, /2009/12, or /2009/12/31
|
||||
#will fill in the appropriate parameters.
|
||||
|
||||
def create(self):
|
||||
#This link will still be available at /create. Defined functions
|
||||
#take precedence over arguments.
|
||||
|
||||
Or as a member of a class:
|
||||
class Blog:
|
||||
_cp_dispatch = cherrypy.popargs('year', 'month', 'day')
|
||||
#...
|
||||
|
||||
The handler argument may be used to mix arguments with built in functions.
|
||||
For instance, the following setup allows different activities at the
|
||||
day, month, and year level:
|
||||
|
||||
class DayHandler:
|
||||
def index(self, year, month, day):
|
||||
#Do something with this day; probably list entries
|
||||
|
||||
def delete(self, year, month, day):
|
||||
#Delete all entries for this day
|
||||
|
||||
@cherrypy.popargs('day', handler=DayHandler())
|
||||
class MonthHandler:
|
||||
def index(self, year, month):
|
||||
#Do something with this month; probably list entries
|
||||
|
||||
def delete(self, year, month):
|
||||
#Delete all entries for this month
|
||||
|
||||
@cherrypy.popargs('month', handler=MonthHandler())
|
||||
class YearHandler:
|
||||
def index(self, year):
|
||||
#Do something with this year
|
||||
|
||||
#...
|
||||
|
||||
@cherrypy.popargs('year', handler=YearHandler())
|
||||
class Root:
|
||||
def index(self):
|
||||
#...
|
||||
|
||||
"""
|
||||
|
||||
# Since keyword arg comes after *args, we have to process it ourselves
|
||||
# for lower versions of python.
|
||||
|
||||
handler = None
|
||||
handler_call = False
|
||||
for k, v in kwargs.items():
|
||||
if k == 'handler':
|
||||
handler = v
|
||||
else:
|
||||
raise TypeError(
|
||||
"cherrypy.popargs() got an unexpected keyword argument '{0}'"
|
||||
.format(k)
|
||||
)
|
||||
|
||||
import inspect
|
||||
|
||||
if handler is not None \
|
||||
and (hasattr(handler, '__call__') or inspect.isclass(handler)):
|
||||
handler_call = True
|
||||
|
||||
def decorated(cls_or_self=None, vpath=None):
|
||||
if inspect.isclass(cls_or_self):
|
||||
# cherrypy.popargs is a class decorator
|
||||
cls = cls_or_self
|
||||
setattr(cls, cherrypy.dispatch.Dispatcher.dispatch_method_name, decorated)
|
||||
return cls
|
||||
|
||||
# We're in the actual function
|
||||
self = cls_or_self
|
||||
parms = {}
|
||||
for arg in args:
|
||||
if not vpath:
|
||||
break
|
||||
parms[arg] = vpath.pop(0)
|
||||
|
||||
if handler is not None:
|
||||
if handler_call:
|
||||
return handler(**parms)
|
||||
else:
|
||||
cherrypy.request.params.update(parms)
|
||||
return handler
|
||||
|
||||
cherrypy.request.params.update(parms)
|
||||
|
||||
# If we are the ultimate handler, then to prevent our _cp_dispatch
|
||||
# from being called again, we will resolve remaining elements through
|
||||
# getattr() directly.
|
||||
if vpath:
|
||||
return getattr(self, vpath.pop(0), None)
|
||||
else:
|
||||
return self
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def url(path="", qs="", script_name=None, base=None, relative=None):
|
||||
"""Create an absolute URL for the given path.
|
||||
|
||||
If 'path' starts with a slash ('/'), this will return
|
||||
(base + script_name + path + qs).
|
||||
If it does not start with a slash, this returns
|
||||
(base + script_name [+ request.path_info] + path + qs).
|
||||
|
||||
If script_name is None, cherrypy.request will be used
|
||||
to find a script_name, if available.
|
||||
|
||||
If base is None, cherrypy.request.base will be used (if available).
|
||||
Note that you can use cherrypy.tools.proxy to change this.
|
||||
|
||||
Finally, note that this function can be used to obtain an absolute URL
|
||||
for the current request path (minus the querystring) by passing no args.
|
||||
If you call url(qs=cherrypy.request.query_string), you should get the
|
||||
original browser URL (assuming no internal redirections).
|
||||
|
||||
If relative is None or not provided, request.app.relative_urls will
|
||||
be used (if available, else False). If False, the output will be an
|
||||
absolute URL (including the scheme, host, vhost, and script_name).
|
||||
If True, the output will instead be a URL that is relative to the
|
||||
current request path, perhaps including '..' atoms. If relative is
|
||||
the string 'server', the output will instead be a URL that is
|
||||
relative to the server root; i.e., it will start with a slash.
|
||||
"""
|
||||
if isinstance(qs, (tuple, list, dict)):
|
||||
qs = _urlencode(qs)
|
||||
if qs:
|
||||
qs = '?' + qs
|
||||
|
||||
if cherrypy.request.app:
|
||||
if not path.startswith("/"):
|
||||
# Append/remove trailing slash from path_info as needed
|
||||
# (this is to support mistyped URL's without redirecting;
|
||||
# if you want to redirect, use tools.trailing_slash).
|
||||
pi = cherrypy.request.path_info
|
||||
if cherrypy.request.is_index is True:
|
||||
if not pi.endswith('/'):
|
||||
pi = pi + '/'
|
||||
elif cherrypy.request.is_index is False:
|
||||
if pi.endswith('/') and pi != '/':
|
||||
pi = pi[:-1]
|
||||
|
||||
if path == "":
|
||||
path = pi
|
||||
else:
|
||||
path = _urljoin(pi, path)
|
||||
|
||||
if script_name is None:
|
||||
script_name = cherrypy.request.script_name
|
||||
if base is None:
|
||||
base = cherrypy.request.base
|
||||
|
||||
newurl = base + script_name + path + qs
|
||||
else:
|
||||
# No request.app (we're being called outside a request).
|
||||
# We'll have to guess the base from server.* attributes.
|
||||
# This will produce very different results from the above
|
||||
# if you're using vhosts or tools.proxy.
|
||||
if base is None:
|
||||
base = cherrypy.server.base()
|
||||
|
||||
path = (script_name or "") + path
|
||||
newurl = base + path + qs
|
||||
|
||||
if './' in newurl:
|
||||
# Normalize the URL by removing ./ and ../
|
||||
atoms = []
|
||||
for atom in newurl.split('/'):
|
||||
if atom == '.':
|
||||
pass
|
||||
elif atom == '..':
|
||||
atoms.pop()
|
||||
else:
|
||||
atoms.append(atom)
|
||||
newurl = '/'.join(atoms)
|
||||
|
||||
# At this point, we should have a fully-qualified absolute URL.
|
||||
|
||||
if relative is None:
|
||||
relative = getattr(cherrypy.request.app, "relative_urls", False)
|
||||
|
||||
# See http://www.ietf.org/rfc/rfc2396.txt
|
||||
if relative == 'server':
|
||||
# "A relative reference beginning with a single slash character is
|
||||
# termed an absolute-path reference, as defined by <abs_path>..."
|
||||
# This is also sometimes called "server-relative".
|
||||
newurl = '/' + '/'.join(newurl.split('/', 3)[3:])
|
||||
elif relative:
|
||||
# "A relative reference that does not begin with a scheme name
|
||||
# or a slash character is termed a relative-path reference."
|
||||
old = url(relative=False).split('/')[:-1]
|
||||
new = newurl.split('/')
|
||||
while old and new:
|
||||
a, b = old[0], new[0]
|
||||
if a != b:
|
||||
break
|
||||
old.pop(0)
|
||||
new.pop(0)
|
||||
new = (['..'] * len(old)) + new
|
||||
newurl = '/'.join(new)
|
||||
|
||||
return newurl
|
106
deps/cherrypy/daemon.py
vendored
Normal file
106
deps/cherrypy/daemon.py
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
"""The CherryPy daemon."""
|
||||
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.process import plugins, servers
|
||||
from cherrypy import Application
|
||||
|
||||
|
||||
def start(configfiles=None, daemonize=False, environment=None,
|
||||
fastcgi=False, scgi=False, pidfile=None, imports=None,
|
||||
cgi=False):
|
||||
"""Subscribe all engine plugins and start the engine."""
|
||||
sys.path = [''] + sys.path
|
||||
for i in imports or []:
|
||||
exec("import %s" % i)
|
||||
|
||||
for c in configfiles or []:
|
||||
cherrypy.config.update(c)
|
||||
# If there's only one app mounted, merge config into it.
|
||||
if len(cherrypy.tree.apps) == 1:
|
||||
for app in cherrypy.tree.apps.values():
|
||||
if isinstance(app, Application):
|
||||
app.merge(c)
|
||||
|
||||
engine = cherrypy.engine
|
||||
|
||||
if environment is not None:
|
||||
cherrypy.config.update({'environment': environment})
|
||||
|
||||
# Only daemonize if asked to.
|
||||
if daemonize:
|
||||
# Don't print anything to stdout/sterr.
|
||||
cherrypy.config.update({'log.screen': False})
|
||||
plugins.Daemonizer(engine).subscribe()
|
||||
|
||||
if pidfile:
|
||||
plugins.PIDFile(engine, pidfile).subscribe()
|
||||
|
||||
if hasattr(engine, "signal_handler"):
|
||||
engine.signal_handler.subscribe()
|
||||
if hasattr(engine, "console_control_handler"):
|
||||
engine.console_control_handler.subscribe()
|
||||
|
||||
if (fastcgi and (scgi or cgi)) or (scgi and cgi):
|
||||
cherrypy.log.error("You may only specify one of the cgi, fastcgi, and "
|
||||
"scgi options.", 'ENGINE')
|
||||
sys.exit(1)
|
||||
elif fastcgi or scgi or cgi:
|
||||
# Turn off autoreload when using *cgi.
|
||||
cherrypy.config.update({'engine.autoreload.on': False})
|
||||
# Turn off the default HTTP server (which is subscribed by default).
|
||||
cherrypy.server.unsubscribe()
|
||||
|
||||
addr = cherrypy.server.bind_addr
|
||||
cls = (
|
||||
servers.FlupFCGIServer if fastcgi else
|
||||
servers.FlupSCGIServer if scgi else
|
||||
servers.FlupCGIServer
|
||||
)
|
||||
f = cls(application=cherrypy.tree, bindAddress=addr)
|
||||
s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr)
|
||||
s.subscribe()
|
||||
|
||||
# Always start the engine; this will start all other services
|
||||
try:
|
||||
engine.start()
|
||||
except:
|
||||
# Assume the error has been logged already via bus.log.
|
||||
sys.exit(1)
|
||||
else:
|
||||
engine.block()
|
||||
|
||||
|
||||
def run():
|
||||
from optparse import OptionParser
|
||||
|
||||
p = OptionParser()
|
||||
p.add_option('-c', '--config', action="append", dest='config',
|
||||
help="specify config file(s)")
|
||||
p.add_option('-d', action="store_true", dest='daemonize',
|
||||
help="run the server as a daemon")
|
||||
p.add_option('-e', '--environment', dest='environment', default=None,
|
||||
help="apply the given config environment")
|
||||
p.add_option('-f', action="store_true", dest='fastcgi',
|
||||
help="start a fastcgi server instead of the default HTTP "
|
||||
"server")
|
||||
p.add_option('-s', action="store_true", dest='scgi',
|
||||
help="start a scgi server instead of the default HTTP server")
|
||||
p.add_option('-x', action="store_true", dest='cgi',
|
||||
help="start a cgi server instead of the default HTTP server")
|
||||
p.add_option('-i', '--import', action="append", dest='imports',
|
||||
help="specify modules to import")
|
||||
p.add_option('-p', '--pidfile', dest='pidfile', default=None,
|
||||
help="store the process id in the given file")
|
||||
p.add_option('-P', '--Path', action="append", dest='Path',
|
||||
help="add the given paths to sys.path")
|
||||
options, args = p.parse_args()
|
||||
|
||||
if options.Path:
|
||||
for p in options.Path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
start(options.config, options.daemonize,
|
||||
options.environment, options.fastcgi, options.scgi,
|
||||
options.pidfile, options.imports, options.cgi)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user