first load, most basic functional state

This commit is contained in:
brad stein 2018-10-21 22:52:24 -05:00
commit 218c4733db
8062 changed files with 1063530 additions and 0 deletions

93
.gitignore vendored Normal file
View File

@ -0,0 +1,93 @@
# Created by https://www.gitignore.io/api/pycharm+all,visualstudiocode
### PyCharm+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PyCharm+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# End of https://www.gitignore.io/api/pycharm+all,visualstudiocode

View File

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,13 @@
# ChannelsDrawShareSite/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import drawshare.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
drawshare.routing.websocket_urlpatterns
)
),
})

View File

@ -0,0 +1,132 @@
"""
Django settings for ChannelsDrawShareSite project.
Generated by 'django-admin startproject' using Django 2.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7q-#4f6)g0(u2d^-ugjt-=w4ac0k$=o8_0z-&d!5yio0j%7n8+'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
'channels',
'drawshare',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'ChannelsDrawShareSite.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'ChannelsDrawShareSite.wsgi.application'
ASGI_APPLICATION = 'ChannelsDrawShareSite.routing.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'

View File

@ -0,0 +1,22 @@
"""ChannelsDrawShareSite URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('drawshare/', include('drawshare.urls')),
path('admin/', admin.site.urls),
]

View File

@ -0,0 +1,16 @@
"""
WSGI config for ChannelsDrawShareSite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ChannelsDrawShareSite.settings')
application = get_wsgi_application()

BIN
db.sqlite3 Normal file

Binary file not shown.

0
drawshare/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

61
drawshare/consumers.py Normal file
View File

@ -0,0 +1,61 @@
# drawshare/consumers.py
from asgiref.sync import async_to_sync
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class DrawConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'drawshare_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
color = text_data_json['color']
x1 = text_data_json['x1']
y1 = text_data_json['y1']
x2 = text_data_json['x2']
y2 = text_data_json['y2']
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'drawshare_message',
'color': color,
'x1': x1,
'y1': y1,
'x2': x2,
'y2': y2
}
)
async def drawshare_message(self, event):
color = event['color']
x1 = event['x1']
y1 = event['y1']
x2 = event['x2']
y2 = event['y2']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'color': color,
'x1': x1,
'y1': y1,
'x2': x2,
'y2': y2,
}))

View File

8
drawshare/routing.py Normal file
View File

@ -0,0 +1,8 @@
# drawshare/routing.py
from django.conf.urls import url
from . import consumers
websocket_urlpatterns = [
url(r'^ws/drawshare/(?P<room_name>[^/]+)/$', consumers.DrawConsumer),
]

View File

@ -0,0 +1,27 @@
<!-- drawshare/templates/drawshare/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>DrawShare Rooms</title>
</head>
<body>
What DrawShare room would you like to enter?<br/>
<input id="room-name-input" type="text" size="30%"/><br/>
<input id="room-name-submit" type="button" value="Enter"/>
<script>
document.querySelector('#room-name-input').focus();
document.querySelector('#room-name-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#room-name-submit').click();
}
};
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
window.location.pathname = '/drawshare/' + roomName + '/';
};
</script>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!-- drawshare/templates/drawshare/room.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>DrawShare Room</title>
</head>
<body>
<select id="draw-color-select" name="colors">
<option value="#ff0000">Red</option>
<option value="#00ff00">Green</option>
<option value="#0000ff">Blue</option>
</select>
<canvas id="draw-canvas" style="border:1px solid #000000;"></canvas>
</body>
<style>
#draw-canvas{
display: block;
margin-left: 7.5%;
margin-top:1.5%
}
#draw-color-select{
display: block;
}
</style>
<script>
window.onload = function(){resize_canvas()};
window.onresize = function(){resize_canvas()};
function resize_canvas() {
let our_canvas = document.getElementById("draw-canvas");
our_canvas.height = window.innerHeight * .8;
our_canvas.width = window.innerWidth * .85;
}
var roomName = {{ room_name_json }};
var drawSocket = new WebSocket(
'ws://' + window.location.host +
'/ws/drawshare/' + roomName + '/');
drawSocket.onmessage = function(e) {
let data = JSON.parse(e.data);
let color = data['color'];
let x1 = data['x1'];
let y1 = data['y1'];
let x2 = data['x2'];
let y2 = data['y2'];
let our_canvas = document.getElementById("draw-canvas");
let ctx = our_canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = 5;
ctx.strokeStyle = color;
ctx.stroke();
console.log("Trying to draw: " + data['color'] + " " + data['x1'] + "," + data['y1'] + " " + data['x2'] + ","
+ data['y2']);
};
drawSocket.onclose = function(e) {console.error('Chat socket closed unexpectedly');};
document.querySelector('#draw-canvas').onclick = function(e) {capture_draw(e)};
function capture_draw(e){
let drawColor = document.querySelector('#draw-color-select');
let x;
let y;
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
} else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
x -= document.querySelector('#draw-canvas').offsetLeft;
y -= document.querySelector('#draw-canvas').offsetTop;
drawSocket.send(JSON.stringify({
'color': drawColor.value,
'x1': x,
'y1': y,
'x2': x + 4 ,
'y2': y + 4
}));
}
</script>
</html>

4
drawshare/tests.py Normal file
View File

@ -0,0 +1,4 @@
from django.test import TestCase
# Create your tests here.

9
drawshare/urls.py Normal file
View File

@ -0,0 +1,9 @@
# drawshare/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'),
]

12
drawshare/views.py Normal file
View File

@ -0,0 +1,12 @@
from django.shortcuts import render
from django.utils.safestring import mark_safe
import json
def index(request):
return render(request, 'drawshare/index.html', {})
def room(request, room_name):
return render(request, 'drawshare/room.html', {
'room_name_json': mark_safe(json.dumps(room_name))
})

15
manage.py Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ChannelsDrawShareSite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

24
requirements.txt Normal file
View File

@ -0,0 +1,24 @@
aioredis==1.1.0
asgiref==2.3.2
async-timeout==3.0.1
attrs==18.2.0
autobahn==18.10.1
Automat==0.7.0
channels==2.1.4
channels-redis==2.3.1
constantly==15.1.0
daphne==2.2.2
Django==2.1.2
hiredis==0.2.0
hyperlink==18.0.0
idna==2.7
incremental==17.5.0
msgpack==0.5.6
PyHamcrest==1.9.0
pytz==2018.5
selenium==3.14.1
six==1.11.0
Twisted==18.9.0
txaio==18.8.1
urllib3==1.24
zope.interface==4.5.0

Binary file not shown.

76
venv/bin/activate Normal file
View File

@ -0,0 +1,76 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
if [ ! "$1" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/home/brad/Development/ChannelsDrawShare/venv"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
if [ "x(venv) " != x ] ; then
PS1="(venv) ${PS1:-}"
else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
fi
fi
export PS1
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi

37
venv/bin/activate.csh Normal file
View File

@ -0,0 +1,37 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV "/home/brad/Development/ChannelsDrawShare/venv"
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
if ("venv" != "") then
set env_name = "venv"
else
if (`basename "VIRTUAL_ENV"` == "__") then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
else
set env_name = `basename "$VIRTUAL_ENV"`
endif
endif
set prompt = "[$env_name] $prompt"
unset env_name
endif
alias pydoc python -m pydoc
rehash

75
venv/bin/activate.fish Normal file
View File

@ -0,0 +1,75 @@
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
# you cannot run it directly
function deactivate -d "Exit virtualenv and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
functions -e fish_prompt
set -e _OLD_FISH_PROMPT_OVERRIDE
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
set -e VIRTUAL_ENV
if test "$argv[1]" != "nondestructive"
# Self destruct!
functions -e deactivate
end
end
# unset irrelevant variables
deactivate nondestructive
set -gx VIRTUAL_ENV "/home/brad/Development/ChannelsDrawShare/venv"
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
# unset PYTHONHOME if set
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# save the current fish_prompt function as the function _old_fish_prompt
functions -c fish_prompt _old_fish_prompt
# with the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command
set -l old_status $status
# Prompt override?
if test -n "(venv) "
printf "%s%s" "(venv) " (set_color normal)
else
# ...Otherwise, prepend env
set -l _checkbase (basename "$VIRTUAL_ENV")
if test $_checkbase = "__"
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
else
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
end
end
# Restore the return status of the previous command.
echo "exit $old_status" | .
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
end

11
venv/bin/automat-visualize Executable file
View File

@ -0,0 +1,11 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from automat._visualize import tool
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(tool())

12
venv/bin/cftp Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','cftp'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'cftp')()
)

12
venv/bin/ckeygen Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','ckeygen'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'ckeygen')()
)

12
venv/bin/conch Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','conch'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'conch')()
)

11
venv/bin/daphne Executable file
View File

@ -0,0 +1,11 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from daphne.cli import CommandLineInterface
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(CommandLineInterface.entrypoint())

11
venv/bin/django-admin Executable file
View File

@ -0,0 +1,11 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from django.core.management import execute_from_command_line
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(execute_from_command_line())

5
venv/bin/django-admin.py Executable file
View File

@ -0,0 +1,5 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
from django.core import management
if __name__ == "__main__":
management.execute_from_command_line()

12
venv/bin/easy_install Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==39.1.0','console_scripts','easy_install'
__requires__ = 'setuptools==39.1.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('setuptools==39.1.0', 'console_scripts', 'easy_install')()
)

12
venv/bin/easy_install-3.6 Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==39.1.0','console_scripts','easy_install-3.6'
__requires__ = 'setuptools==39.1.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('setuptools==39.1.0', 'console_scripts', 'easy_install-3.6')()
)

12
venv/bin/mailmail Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','mailmail'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'mailmail')()
)

12
venv/bin/pip Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip'
__requires__ = 'pip==10.0.1'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pip==10.0.1', 'console_scripts', 'pip')()
)

12
venv/bin/pip3 Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip3'
__requires__ = 'pip==10.0.1'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pip==10.0.1', 'console_scripts', 'pip3')()
)

12
venv/bin/pip3.6 Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip3.6'
__requires__ = 'pip==10.0.1'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pip==10.0.1', 'console_scripts', 'pip3.6')()
)

12
venv/bin/pyhtmlizer Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','pyhtmlizer'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'pyhtmlizer')()
)

BIN
venv/bin/python Executable file

Binary file not shown.

BIN
venv/bin/python3 Executable file

Binary file not shown.

BIN
venv/bin/python3.6 Executable file

Binary file not shown.

12
venv/bin/tkconch Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','tkconch'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'tkconch')()
)

12
venv/bin/trial Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','trial'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'trial')()
)

12
venv/bin/twist Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','twist'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'twist')()
)

12
venv/bin/twistd Executable file
View File

@ -0,0 +1,12 @@
#!/home/brad/Development/ChannelsDrawShare/venv/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==18.9.0','console_scripts','twistd'
__requires__ = 'Twisted==18.9.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('Twisted==18.9.0', 'console_scripts', 'twistd')()
)

View File

@ -0,0 +1,460 @@
Automat
=======
.. image:: https://readthedocs.org/projects/automat/badge/?version=stable
:target: http://automat.readthedocs.io/en/latest/
:alt: Documentation Status
.. image:: https://travis-ci.org/glyph/automat.svg?branch=master
:target: https://travis-ci.org/glyph/automat
:alt: Build Status
.. image:: https://coveralls.io/repos/glyph/automat/badge.png
:target: https://coveralls.io/r/glyph/automat
:alt: Coverage Status
Self-service finite-state machines for the programmer on the go.
----------------------------------------------------------------
Automat is a library for concise, idiomatic Python expression of finite-state
automata (particularly deterministic finite-state transducers).
Read more here, or on `Read the Docs <https://automat.readthedocs.io/>`_\ , or watch the following videos for an overview and presentation
Overview and presentation by **Glyph Lefkowitz** at the first talk of the first Pyninsula meetup, on February 21st, 2017:
.. image:: https://img.youtube.com/vi/0wOZBpD1VVk/0.jpg
:target: https://www.youtube.com/watch?v=0wOZBpD1VVk
:alt: Glyph Lefkowitz - Automat - Pyninsula #0
Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:
.. image:: https://img.youtube.com/vi/TedUKXhu9kE/0.jpg
:target: https://www.youtube.com/watch?v=TedUKXhu9kE
:alt: Clinton Roy - State Machines - Pycon Australia 2017
Why use state machines?
^^^^^^^^^^^^^^^^^^^^^^^
Sometimes you have to create an object whose behavior varies with its state,
but still wishes to present a consistent interface to its callers.
For example, let's say you're writing the software for a coffee machine. It
has a lid that can be opened or closed, a chamber for water, a chamber for
coffee beans, and a button for "brew".
There are a number of possible states for the coffee machine. It might or
might not have water. It might or might not have beans. The lid might be open
or closed. The "brew" button should only actually attempt to brew coffee in
one of these configurations, and the "open lid" button should only work if the
coffee is not, in fact, brewing.
With diligence and attention to detail, you can implement this correctly using
a collection of attributes on an object; ``has_water``\ , ``has_beans``\ ,
``is_lid_open`` and so on. However, you have to keep all these attributes
consistent. As the coffee maker becomes more complex - perhaps you add an
additional chamber for flavorings so you can make hazelnut coffee, for
example - you have to keep adding more and more checks and more and more
reasoning about which combinations of states are allowed.
Rather than adding tedious 'if' checks to every single method to make sure that
each of these flags are exactly what you expect, you can use a state machine to
ensure that if your code runs at all, it will be run with all the required
values initialized, because they have to be called in the order you declare
them.
You can read about state machines and their advantages for Python programmers
in considerably more detail
`in this excellent series of articles from ClusterHQ <https://clusterhq.com/blog/what-is-a-state-machine/>`_.
What makes Automat different?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are
`dozens of libraries on PyPI implementing state machines <https://pypi.org/search/?q=finite+state+machine>`_.
So it behooves me to say why yet another one would be a good idea.
Automat is designed around this principle: while organizing your code around
state machines is a good idea, your callers don't, and shouldn't have to, care
that you've done so. In Python, the "input" to a stateful system is a method
call; the "output" may be a method call, if you need to invoke a side effect,
or a return value, if you are just performing a computation in memory. Most
other state-machine libraries require you to explicitly create an input object,
provide that object to a generic "input" method, and then receive results,
sometimes in terms of that library's interfaces and sometimes in terms of
classes you define yourself.
For example, a snippet of the coffee-machine example above might be implemented
as follows in naive Python:
.. code-block:: python
class CoffeeMachine(object):
def brew_button(self):
if self.has_water and self.has_beans and not self.is_lid_open:
self.heat_the_heating_element()
# ...
With Automat, you'd create a class with a ``MethodicalMachine`` attribute:
.. code-block:: python
from automat import MethodicalMachine
class CoffeeBrewer(object):
_machine = MethodicalMachine()
and then you would break the above logic into two pieces - the ``brew_button``
*input*\ , declared like so:
.. code-block:: python
@_machine.input()
def brew_button(self):
"The user pressed the 'brew' button."
It wouldn't do any good to declare a method *body* on this, however, because
input methods don't actually execute their bodies when called; doing actual
work is the *output*\ 's job:
.. code-block:: python
@_machine.output()
def _heat_the_heating_element(self):
"Heat up the heating element, which should cause coffee to happen."
self._heating_element.turn_on()
As well as a couple of *states* - and for simplicity's sake let's say that the
only two states are ``have_beans`` and ``dont_have_beans``\ :
.. code-block:: python
@_machine.state()
def have_beans(self):
"In this state, you have some beans."
@_machine.state(initial=True)
def dont_have_beans(self):
"In this state, you don't have any beans."
``dont_have_beans`` is the ``initial`` state because ``CoffeeBrewer`` starts without beans
in it.
(And another input to put some beans in:)
.. code-block:: python
@_machine.input()
def put_in_beans(self):
"The user put in some beans."
Finally, you hook everything together with the ``upon`` method of the functions
decorated with ``_machine.state``\ :
.. code-block:: python
# When we don't have beans, upon putting in beans, we will then have beans
# (and produce no output)
dont_have_beans.upon(put_in_beans, enter=have_beans, outputs=[])
# When we have beans, upon pressing the brew button, we will then not have
# beans any more (as they have been entered into the brewing chamber) and
# our output will be heating the heating element.
have_beans.upon(brew_button, enter=dont_have_beans,
outputs=[_heat_the_heating_element])
To *users* of this coffee machine class though, it still looks like a POPO
(Plain Old Python Object):
.. code-block:: python
>>> coffee_machine = CoffeeMachine()
>>> coffee_machine.put_in_beans()
>>> coffee_machine.brew_button()
All of the *inputs* are provided by calling them like methods, all of the
*outputs* are automatically invoked when they are produced according to the
outputs specified to ``upon`` and all of the states are simply opaque tokens -
although the fact that they're defined as methods like inputs and outputs
allows you to put docstrings on them easily to document them.
How do I get the current state of a state machine?
--------------------------------------------------
Don't do that.
One major reason for having a state machine is that you want the callers of the
state machine to just provide the appropriate input to the machine at the
appropriate time, and *not have to check themselves* what state the machine is
in. So if you are tempted to write some code like this:
.. code-block:: python
if connection_state_machine.state == "CONNECTED":
connection_state_machine.send_message()
else:
print("not connected")
Instead, just make your calling code do this:
.. code-block:: python
connection_state_machine.send_message()
and then change your state machine to look like this:
.. code-block:: python
@_machine.state()
def connected(self):
"connected"
@_machine.state()
def not_connected(self):
"not connected"
@_machine.input()
def send_message(self):
"send a message"
@_machine.output()
def _actually_send_message(self):
self._transport.send(b"message")
@_machine.output()
def _report_sending_failure(self):
print("not connected")
connected.upon(send_message, enter=connected, [_actually_send_message])
not_connected.upon(send_message, enter=not_connected, [_report_sending_failure])
so that the responsibility for knowing which state the state machine is in
remains within the state machine itself.
Input for Inputs and Output for Outputs
---------------------------------------
Quite often you want to be able to pass parameters to your methods, as well as
inspecting their results. For example, when you brew the coffee, you might
expect a cup of coffee to result, and you would like to see what kind of coffee
it is. And if you were to put delicious hand-roasted small-batch artisanal
beans into the machine, you would expect a *better* cup of coffee than if you
were to use mass-produced beans. You would do this in plain old Python by
adding a parameter, so that's how you do it in Automat as well.
.. code-block:: python
@_machine.input()
def put_in_beans(self, beans):
"The user put in some beans."
However, one important difference here is that *we can't add any
implementation code to the input method*. Inputs are purely a declaration of
the interface; the behavior must all come from outputs. Therefore, the change
in the state of the coffee machine must be represented as an output. We can
add an output method like this:
.. code-block:: python
@_machine.output()
def _save_beans(self, beans):
"The beans are now in the machine; save them."
self._beans = beans
and then connect it to the ``put_in_beans`` by changing the transition from
``dont_have_beans`` to ``have_beans`` like so:
.. code-block:: python
dont_have_beans.upon(put_in_beans, enter=have_beans,
outputs=[_save_beans])
Now, when you call:
.. code-block:: python
coffee_machine.put_in_beans("real good beans")
the machine will remember the beans for later.
So how do we get the beans back out again? One of our outputs needs to have a
return value. It would make sense if our ``brew_button`` method returned the cup
of coffee that it made, so we should add an output. So, in addition to heating
the heating element, let's add a return value that describes the coffee. First
a new output:
.. code-block:: python
@_machine.output()
def _describe_coffee(self):
return "A cup of coffee made with {}.".format(self._beans)
Note that we don't need to check first whether ``self._beans`` exists or not,
because we can only reach this output method if the state machine says we've
gone through a set of states that sets this attribute.
Now, we need to hook up ``_describe_coffee`` to the process of brewing, so change
the brewing transition to:
.. code-block:: python
have_beans.upon(brew_button, enter=dont_have_beans,
outputs=[_heat_the_heating_element,
_describe_coffee])
Now, we can call it:
.. code-block:: python
>>> coffee_machine.brew_button()
[None, 'A cup of coffee made with real good beans.']
Except... wait a second, what's that ``None`` doing there?
Since every input can produce multiple outputs, in automat, the default return
value from every input invocation is a ``list``. In this case, we have both
``_heat_the_heating_element`` and ``_describe_coffee`` outputs, so we're seeing
both of their return values. However, this can be customized, with the
``collector`` argument to ``upon``\ ; the ``collector`` is a callable which takes an
iterable of all the outputs' return values and "collects" a single return value
to return to the caller of the state machine.
In this case, we only care about the last output, so we can adjust the call to
``upon`` like this:
.. code-block:: python
have_beans.upon(brew_button, enter=dont_have_beans,
outputs=[_heat_the_heating_element,
_describe_coffee],
collector=lambda iterable: list(iterable)[-1]
)
And now, we'll get just the return value we want:
.. code-block:: python
>>> coffee_machine.brew_button()
'A cup of coffee made with real good beans.'
If I can't get the state of the state machine, how can I save it to (a database, an API response, a file on disk...)
--------------------------------------------------------------------------------------------------------------------
There are APIs for serializing the state machine.
First, you have to decide on a persistent representation of each state, via the
``serialized=`` argument to the ``MethodicalMachine.state()`` decorator.
Let's take this very simple "light switch" state machine, which can be on or
off, and flipped to reverse its state:
.. code-block:: python
class LightSwitch(object):
_machine = MethodicalMachine()
@_machine.state(serialized="on")
def on_state(self):
"the switch is on"
@_machine.state(serialized="off", initial=True)
def off_state(self):
"the switch is off"
@_machine.input()
def flip(self):
"flip the switch"
on_state.upon(flip, enter=off_state, outputs=[])
off_state.upon(flip, enter=on_state, outputs=[])
In this case, we've chosen a serialized representation for each state via the
``serialized`` argument. The on state is represented by the string ``"on"``\ , and
the off state is represented by the string ``"off"``.
Now, let's just add an input that lets us tell if the switch is on or not.
.. code-block:: python
@_machine.input()
def query_power(self):
"return True if powered, False otherwise"
@_machine.output()
def _is_powered(self):
return True
@_machine.output()
def _not_powered(self):
return False
on_state.upon(query_power, enter=on_state, outputs=[_is_powered],
collector=next)
off_state.upon(query_power, enter=off_state, outputs=[_not_powered],
collector=next)
To save the state, we have the ``MethodicalMachine.serializer()`` method. A
method decorated with ``@serializer()`` gets an extra argument injected at the
beginning of its argument list: the serialized identifier for the state. In
this case, either ``"on"`` or ``"off"``. Since state machine output methods can
also affect other state on the object, a serializer method is expected to
return *all* relevant state for serialization.
For our simple light switch, such a method might look like this:
.. code-block:: python
@_machine.serializer()
def save(self, state):
return {"is-it-on": state}
Serializers can be public methods, and they can return whatever you like. If
necessary, you can have different serializers - just multiple methods decorated
with ``@_machine.serializer()`` - for different formats; return one data-structure
for JSON, one for XML, one for a database row, and so on.
When it comes time to unserialize, though, you generally want a private method,
because an unserializer has to take a not-fully-initialized instance and
populate it with state. It is expected to *return* the serialized machine
state token that was passed to the serializer, but it can take whatever
arguments you like. Of course, in order to return that, it probably has to
take it somewhere in its arguments, so it will generally take whatever a paired
serializer has returned as an argument.
So our unserializer would look like this:
.. code-block:: python
@_machine.unserializer()
def _restore(self, blob):
return blob["is-it-on"]
Generally you will want a classmethod deserialization constructor which you
write yourself to call this, so that you know how to create an instance of your
own object, like so:
.. code-block:: python
@classmethod
def from_blob(cls, blob):
self = cls()
self._restore(blob)
return self
Saving and loading our ``LightSwitch`` along with its state-machine state can now
be accomplished as follows:
.. code-block:: python
>>> switch1 = LightSwitch()
>>> switch1.query_power()
False
>>> switch1.flip()
[]
>>> switch1.query_power()
True
>>> blob = switch1.save()
>>> switch2 = LightSwitch.from_blob(blob)
>>> switch2.query_power()
True
More comprehensive (tested, working) examples are present in ``docs/examples``.
Go forth and machine all the state!

View File

@ -0,0 +1 @@
pip

View File

@ -0,0 +1,488 @@
Metadata-Version: 2.0
Name: Automat
Version: 0.7.0
Summary: Self-service finite-state machines for the programmer on the go.
Home-page: https://github.com/glyph/Automat
Author: Glyph
Author-email: glyph@twistedmatrix.com
License: MIT
Keywords: fsm finite state machine automata
Platform: UNKNOWN
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 :: 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: Programming Language :: Python :: 3.6
Provides-Extra: visualize
Requires-Dist: attrs (>=16.1.0)
Requires-Dist: six
Provides-Extra: visualize
Requires-Dist: graphviz (>0.5.1); extra == 'visualize'
Requires-Dist: Twisted (>=16.1.1); extra == 'visualize'
Automat
=======
.. image:: https://readthedocs.org/projects/automat/badge/?version=stable
:target: http://automat.readthedocs.io/en/latest/
:alt: Documentation Status
.. image:: https://travis-ci.org/glyph/automat.svg?branch=master
:target: https://travis-ci.org/glyph/automat
:alt: Build Status
.. image:: https://coveralls.io/repos/glyph/automat/badge.png
:target: https://coveralls.io/r/glyph/automat
:alt: Coverage Status
Self-service finite-state machines for the programmer on the go.
----------------------------------------------------------------
Automat is a library for concise, idiomatic Python expression of finite-state
automata (particularly deterministic finite-state transducers).
Read more here, or on `Read the Docs <https://automat.readthedocs.io/>`_\ , or watch the following videos for an overview and presentation
Overview and presentation by **Glyph Lefkowitz** at the first talk of the first Pyninsula meetup, on February 21st, 2017:
.. image:: https://img.youtube.com/vi/0wOZBpD1VVk/0.jpg
:target: https://www.youtube.com/watch?v=0wOZBpD1VVk
:alt: Glyph Lefkowitz - Automat - Pyninsula #0
Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:
.. image:: https://img.youtube.com/vi/TedUKXhu9kE/0.jpg
:target: https://www.youtube.com/watch?v=TedUKXhu9kE
:alt: Clinton Roy - State Machines - Pycon Australia 2017
Why use state machines?
^^^^^^^^^^^^^^^^^^^^^^^
Sometimes you have to create an object whose behavior varies with its state,
but still wishes to present a consistent interface to its callers.
For example, let's say you're writing the software for a coffee machine. It
has a lid that can be opened or closed, a chamber for water, a chamber for
coffee beans, and a button for "brew".
There are a number of possible states for the coffee machine. It might or
might not have water. It might or might not have beans. The lid might be open
or closed. The "brew" button should only actually attempt to brew coffee in
one of these configurations, and the "open lid" button should only work if the
coffee is not, in fact, brewing.
With diligence and attention to detail, you can implement this correctly using
a collection of attributes on an object; ``has_water``\ , ``has_beans``\ ,
``is_lid_open`` and so on. However, you have to keep all these attributes
consistent. As the coffee maker becomes more complex - perhaps you add an
additional chamber for flavorings so you can make hazelnut coffee, for
example - you have to keep adding more and more checks and more and more
reasoning about which combinations of states are allowed.
Rather than adding tedious 'if' checks to every single method to make sure that
each of these flags are exactly what you expect, you can use a state machine to
ensure that if your code runs at all, it will be run with all the required
values initialized, because they have to be called in the order you declare
them.
You can read about state machines and their advantages for Python programmers
in considerably more detail
`in this excellent series of articles from ClusterHQ <https://clusterhq.com/blog/what-is-a-state-machine/>`_.
What makes Automat different?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are
`dozens of libraries on PyPI implementing state machines <https://pypi.org/search/?q=finite+state+machine>`_.
So it behooves me to say why yet another one would be a good idea.
Automat is designed around this principle: while organizing your code around
state machines is a good idea, your callers don't, and shouldn't have to, care
that you've done so. In Python, the "input" to a stateful system is a method
call; the "output" may be a method call, if you need to invoke a side effect,
or a return value, if you are just performing a computation in memory. Most
other state-machine libraries require you to explicitly create an input object,
provide that object to a generic "input" method, and then receive results,
sometimes in terms of that library's interfaces and sometimes in terms of
classes you define yourself.
For example, a snippet of the coffee-machine example above might be implemented
as follows in naive Python:
.. code-block:: python
class CoffeeMachine(object):
def brew_button(self):
if self.has_water and self.has_beans and not self.is_lid_open:
self.heat_the_heating_element()
# ...
With Automat, you'd create a class with a ``MethodicalMachine`` attribute:
.. code-block:: python
from automat import MethodicalMachine
class CoffeeBrewer(object):
_machine = MethodicalMachine()
and then you would break the above logic into two pieces - the ``brew_button``
*input*\ , declared like so:
.. code-block:: python
@_machine.input()
def brew_button(self):
"The user pressed the 'brew' button."
It wouldn't do any good to declare a method *body* on this, however, because
input methods don't actually execute their bodies when called; doing actual
work is the *output*\ 's job:
.. code-block:: python
@_machine.output()
def _heat_the_heating_element(self):
"Heat up the heating element, which should cause coffee to happen."
self._heating_element.turn_on()
As well as a couple of *states* - and for simplicity's sake let's say that the
only two states are ``have_beans`` and ``dont_have_beans``\ :
.. code-block:: python
@_machine.state()
def have_beans(self):
"In this state, you have some beans."
@_machine.state(initial=True)
def dont_have_beans(self):
"In this state, you don't have any beans."
``dont_have_beans`` is the ``initial`` state because ``CoffeeBrewer`` starts without beans
in it.
(And another input to put some beans in:)
.. code-block:: python
@_machine.input()
def put_in_beans(self):
"The user put in some beans."
Finally, you hook everything together with the ``upon`` method of the functions
decorated with ``_machine.state``\ :
.. code-block:: python
# When we don't have beans, upon putting in beans, we will then have beans
# (and produce no output)
dont_have_beans.upon(put_in_beans, enter=have_beans, outputs=[])
# When we have beans, upon pressing the brew button, we will then not have
# beans any more (as they have been entered into the brewing chamber) and
# our output will be heating the heating element.
have_beans.upon(brew_button, enter=dont_have_beans,
outputs=[_heat_the_heating_element])
To *users* of this coffee machine class though, it still looks like a POPO
(Plain Old Python Object):
.. code-block:: python
>>> coffee_machine = CoffeeMachine()
>>> coffee_machine.put_in_beans()
>>> coffee_machine.brew_button()
All of the *inputs* are provided by calling them like methods, all of the
*outputs* are automatically invoked when they are produced according to the
outputs specified to ``upon`` and all of the states are simply opaque tokens -
although the fact that they're defined as methods like inputs and outputs
allows you to put docstrings on them easily to document them.
How do I get the current state of a state machine?
--------------------------------------------------
Don't do that.
One major reason for having a state machine is that you want the callers of the
state machine to just provide the appropriate input to the machine at the
appropriate time, and *not have to check themselves* what state the machine is
in. So if you are tempted to write some code like this:
.. code-block:: python
if connection_state_machine.state == "CONNECTED":
connection_state_machine.send_message()
else:
print("not connected")
Instead, just make your calling code do this:
.. code-block:: python
connection_state_machine.send_message()
and then change your state machine to look like this:
.. code-block:: python
@_machine.state()
def connected(self):
"connected"
@_machine.state()
def not_connected(self):
"not connected"
@_machine.input()
def send_message(self):
"send a message"
@_machine.output()
def _actually_send_message(self):
self._transport.send(b"message")
@_machine.output()
def _report_sending_failure(self):
print("not connected")
connected.upon(send_message, enter=connected, [_actually_send_message])
not_connected.upon(send_message, enter=not_connected, [_report_sending_failure])
so that the responsibility for knowing which state the state machine is in
remains within the state machine itself.
Input for Inputs and Output for Outputs
---------------------------------------
Quite often you want to be able to pass parameters to your methods, as well as
inspecting their results. For example, when you brew the coffee, you might
expect a cup of coffee to result, and you would like to see what kind of coffee
it is. And if you were to put delicious hand-roasted small-batch artisanal
beans into the machine, you would expect a *better* cup of coffee than if you
were to use mass-produced beans. You would do this in plain old Python by
adding a parameter, so that's how you do it in Automat as well.
.. code-block:: python
@_machine.input()
def put_in_beans(self, beans):
"The user put in some beans."
However, one important difference here is that *we can't add any
implementation code to the input method*. Inputs are purely a declaration of
the interface; the behavior must all come from outputs. Therefore, the change
in the state of the coffee machine must be represented as an output. We can
add an output method like this:
.. code-block:: python
@_machine.output()
def _save_beans(self, beans):
"The beans are now in the machine; save them."
self._beans = beans
and then connect it to the ``put_in_beans`` by changing the transition from
``dont_have_beans`` to ``have_beans`` like so:
.. code-block:: python
dont_have_beans.upon(put_in_beans, enter=have_beans,
outputs=[_save_beans])
Now, when you call:
.. code-block:: python
coffee_machine.put_in_beans("real good beans")
the machine will remember the beans for later.
So how do we get the beans back out again? One of our outputs needs to have a
return value. It would make sense if our ``brew_button`` method returned the cup
of coffee that it made, so we should add an output. So, in addition to heating
the heating element, let's add a return value that describes the coffee. First
a new output:
.. code-block:: python
@_machine.output()
def _describe_coffee(self):
return "A cup of coffee made with {}.".format(self._beans)
Note that we don't need to check first whether ``self._beans`` exists or not,
because we can only reach this output method if the state machine says we've
gone through a set of states that sets this attribute.
Now, we need to hook up ``_describe_coffee`` to the process of brewing, so change
the brewing transition to:
.. code-block:: python
have_beans.upon(brew_button, enter=dont_have_beans,
outputs=[_heat_the_heating_element,
_describe_coffee])
Now, we can call it:
.. code-block:: python
>>> coffee_machine.brew_button()
[None, 'A cup of coffee made with real good beans.']
Except... wait a second, what's that ``None`` doing there?
Since every input can produce multiple outputs, in automat, the default return
value from every input invocation is a ``list``. In this case, we have both
``_heat_the_heating_element`` and ``_describe_coffee`` outputs, so we're seeing
both of their return values. However, this can be customized, with the
``collector`` argument to ``upon``\ ; the ``collector`` is a callable which takes an
iterable of all the outputs' return values and "collects" a single return value
to return to the caller of the state machine.
In this case, we only care about the last output, so we can adjust the call to
``upon`` like this:
.. code-block:: python
have_beans.upon(brew_button, enter=dont_have_beans,
outputs=[_heat_the_heating_element,
_describe_coffee],
collector=lambda iterable: list(iterable)[-1]
)
And now, we'll get just the return value we want:
.. code-block:: python
>>> coffee_machine.brew_button()
'A cup of coffee made with real good beans.'
If I can't get the state of the state machine, how can I save it to (a database, an API response, a file on disk...)
--------------------------------------------------------------------------------------------------------------------
There are APIs for serializing the state machine.
First, you have to decide on a persistent representation of each state, via the
``serialized=`` argument to the ``MethodicalMachine.state()`` decorator.
Let's take this very simple "light switch" state machine, which can be on or
off, and flipped to reverse its state:
.. code-block:: python
class LightSwitch(object):
_machine = MethodicalMachine()
@_machine.state(serialized="on")
def on_state(self):
"the switch is on"
@_machine.state(serialized="off", initial=True)
def off_state(self):
"the switch is off"
@_machine.input()
def flip(self):
"flip the switch"
on_state.upon(flip, enter=off_state, outputs=[])
off_state.upon(flip, enter=on_state, outputs=[])
In this case, we've chosen a serialized representation for each state via the
``serialized`` argument. The on state is represented by the string ``"on"``\ , and
the off state is represented by the string ``"off"``.
Now, let's just add an input that lets us tell if the switch is on or not.
.. code-block:: python
@_machine.input()
def query_power(self):
"return True if powered, False otherwise"
@_machine.output()
def _is_powered(self):
return True
@_machine.output()
def _not_powered(self):
return False
on_state.upon(query_power, enter=on_state, outputs=[_is_powered],
collector=next)
off_state.upon(query_power, enter=off_state, outputs=[_not_powered],
collector=next)
To save the state, we have the ``MethodicalMachine.serializer()`` method. A
method decorated with ``@serializer()`` gets an extra argument injected at the
beginning of its argument list: the serialized identifier for the state. In
this case, either ``"on"`` or ``"off"``. Since state machine output methods can
also affect other state on the object, a serializer method is expected to
return *all* relevant state for serialization.
For our simple light switch, such a method might look like this:
.. code-block:: python
@_machine.serializer()
def save(self, state):
return {"is-it-on": state}
Serializers can be public methods, and they can return whatever you like. If
necessary, you can have different serializers - just multiple methods decorated
with ``@_machine.serializer()`` - for different formats; return one data-structure
for JSON, one for XML, one for a database row, and so on.
When it comes time to unserialize, though, you generally want a private method,
because an unserializer has to take a not-fully-initialized instance and
populate it with state. It is expected to *return* the serialized machine
state token that was passed to the serializer, but it can take whatever
arguments you like. Of course, in order to return that, it probably has to
take it somewhere in its arguments, so it will generally take whatever a paired
serializer has returned as an argument.
So our unserializer would look like this:
.. code-block:: python
@_machine.unserializer()
def _restore(self, blob):
return blob["is-it-on"]
Generally you will want a classmethod deserialization constructor which you
write yourself to call this, so that you know how to create an instance of your
own object, like so:
.. code-block:: python
@classmethod
def from_blob(cls, blob):
self = cls()
self._restore(blob)
return self
Saving and loading our ``LightSwitch`` along with its state-machine state can now
be accomplished as follows:
.. code-block:: python
>>> switch1 = LightSwitch()
>>> switch1.query_power()
False
>>> switch1.flip()
[]
>>> switch1.query_power()
True
>>> blob = switch1.save()
>>> switch2 = LightSwitch.from_blob(blob)
>>> switch2.query_power()
True
More comprehensive (tested, working) examples are present in ``docs/examples``.
Go forth and machine all the state!

View File

@ -0,0 +1,33 @@
Automat-0.7.0.dist-info/DESCRIPTION.rst,sha256=phbz6jq_IJGCmem_x99TI5NgRchFvisJkI4D7bdCWjY,16902
Automat-0.7.0.dist-info/METADATA,sha256=4f2JEI0XXM9WK-89oEZurg2zFRKtvYWUP7L1_1miOQg,17944
Automat-0.7.0.dist-info/RECORD,,
Automat-0.7.0.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110
Automat-0.7.0.dist-info/entry_points.txt,sha256=i4tDM5qwy3v1KIN7ETS2XRVcHkThhkq7ZFTPuyP6BpA,63
Automat-0.7.0.dist-info/metadata.json,sha256=Y_IJvL36UlnAhpc1DrUBOxl7tA3B-VTAJbb-qcdH77k,1294
Automat-0.7.0.dist-info/top_level.txt,sha256=vg4zAOyhP_3YCmpKZLNgFw1uMF3lC_b6TKsdz7jBSpI,8
automat/__init__.py,sha256=ec8PILBwt35xyzsstU9kx8p5ADi6KX9d1rBLZsocN_Y,169
automat/_core.py,sha256=RmCHze92zB7yLT_Fqb8iCa5hCUY1jF1wPqqjc6CZWH4,4825
automat/_discover.py,sha256=ye7NHLZkrwYsPmBpTIyJuJ-VRCmwkOUpA0is-A81z04,4367
automat/_introspection.py,sha256=Sr6VipsvKKYNpY7YKZSpIsqfTMuT-xRXS75lF7xbFHY,1135
automat/_methodical.py,sha256=AEWagTYaAF8PCXsYoQRYeRn07Jg5fdKDFuAlPvGYHUM,15932
automat/_visualize.py,sha256=jY8HkzaGdMoXB7LavvaneW4GdtBN6PRShl7-4OXDvss,6335
automat/_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
automat/_test/test_core.py,sha256=75kEU7N1jHNmZcz0mcm5e2_UiCmi7FszU7Lqk6bjRfM,2848
automat/_test/test_discover.py,sha256=O9ndAdRAC8uO0uDhioz3e45EsJCF19jQZESj0RBC7ZM,21846
automat/_test/test_methodical.py,sha256=t1CAKtT1fs-FoKMQxXl4ky6_6pgpG7lGDbyQrb_vIeo,18856
automat/_test/test_trace.py,sha256=Mx1B8QgaE7QFk6blTie2j-Vx95hTV-zySnlxLalt8ek,3279
automat/_test/test_visualize.py,sha256=8ErNYxovTiDyZkYkoP1BcyEazU_s0YQ3NHdfH9OihAg,13744
../../../bin/automat-visualize,sha256=RrERY6KtvjdUjuMSsUuqUvhibIlDbDYaCebUlbXm-2s,261
Automat-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
automat/__pycache__/_introspection.cpython-36.pyc,,
automat/__pycache__/_core.cpython-36.pyc,,
automat/__pycache__/_discover.cpython-36.pyc,,
automat/__pycache__/_methodical.cpython-36.pyc,,
automat/__pycache__/__init__.cpython-36.pyc,,
automat/__pycache__/_visualize.cpython-36.pyc,,
automat/_test/__pycache__/test_core.cpython-36.pyc,,
automat/_test/__pycache__/test_visualize.cpython-36.pyc,,
automat/_test/__pycache__/test_trace.cpython-36.pyc,,
automat/_test/__pycache__/test_discover.cpython-36.pyc,,
automat/_test/__pycache__/__init__.cpython-36.pyc,,
automat/_test/__pycache__/test_methodical.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[console_scripts]
automat-visualize = automat._visualize:tool

View File

@ -0,0 +1 @@
{"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6"], "extensions": {"python.commands": {"wrap_console": {"automat-visualize": "automat._visualize:tool"}}, "python.details": {"contacts": [{"email": "glyph@twistedmatrix.com", "name": "Glyph", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/glyph/Automat"}}, "python.exports": {"console_scripts": {"automat-visualize": "automat._visualize:tool"}}}, "extras": ["visualize"], "generator": "bdist_wheel (0.30.0)", "keywords": ["fsm", "finite", "state", "machine", "automata"], "license": "MIT", "metadata_version": "2.0", "name": "Automat", "run_requires": [{"extra": "visualize", "requires": ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"]}, {"requires": ["attrs (>=16.1.0)", "six"]}], "summary": "Self-service finite-state machines for the programmer on the go.", "version": "0.7.0"}

View File

@ -0,0 +1 @@
automat

View File

@ -0,0 +1,46 @@
Django is a high-level Python Web framework that encourages rapid development
and clean, pragmatic design. Thanks for checking it out.
All documentation is in the "``docs``" directory and online at
https://docs.djangoproject.com/en/stable/. If you're just getting started,
here's how we recommend you read the docs:
* First, read ``docs/intro/install.txt`` for instructions on installing Django.
* Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
``docs/intro/tutorial02.txt``, etc.).
* If you want to set up an actual deployment server, read
``docs/howto/deployment/index.txt`` for instructions.
* You'll probably want to read through the topical guides (in ``docs/topics``)
next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
problems, and check out the reference (``docs/ref``) for gory details.
* See ``docs/README`` for instructions on building an HTML version of the docs.
Docs are updated rigorously. If you find any problems in the docs, or think
they should be clarified in any way, please take 30 seconds to fill out a
ticket here: https://code.djangoproject.com/newticket
To get more help:
* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang out
there. Read the archives at https://botbot.me/freenode/django/. See
https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're new to IRC.
* Join the django-users mailing list, or read the archives, at
https://groups.google.com/group/django-users.
To contribute to Django:
* Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
information about getting involved.
To run Django's test suite:
* Follow the instructions in the "Unit tests" section of
``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests

View File

@ -0,0 +1 @@
pip

View File

@ -0,0 +1,27 @@
Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,84 @@
Metadata-Version: 2.0
Name: Django
Version: 2.1.2
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
Author-email: foundation@djangoproject.com
License: BSD
Project-URL: Documentation, https://docs.djangoproject.com/
Project-URL: Funding, https://www.djangoproject.com/fundraising/
Project-URL: Source, https://github.com/django/django
Project-URL: Tracker, https://code.djangoproject.com/
Description-Content-Type: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.5
Requires-Dist: pytz
Provides-Extra: argon2
Requires-Dist: argon2-cffi (>=16.1.0); extra == 'argon2'
Provides-Extra: bcrypt
Requires-Dist: bcrypt; extra == 'bcrypt'
Django is a high-level Python Web framework that encourages rapid development
and clean, pragmatic design. Thanks for checking it out.
All documentation is in the "``docs``" directory and online at
https://docs.djangoproject.com/en/stable/. If you're just getting started,
here's how we recommend you read the docs:
* First, read ``docs/intro/install.txt`` for instructions on installing Django.
* Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
``docs/intro/tutorial02.txt``, etc.).
* If you want to set up an actual deployment server, read
``docs/howto/deployment/index.txt`` for instructions.
* You'll probably want to read through the topical guides (in ``docs/topics``)
next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
problems, and check out the reference (``docs/ref``) for gory details.
* See ``docs/README`` for instructions on building an HTML version of the docs.
Docs are updated rigorously. If you find any problems in the docs, or think
they should be clarified in any way, please take 30 seconds to fill out a
ticket here: https://code.djangoproject.com/newticket
To get more help:
* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang out
there. Read the archives at https://botbot.me/freenode/django/. See
https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're new to IRC.
* Join the django-users mailing list, or read the archives, at
https://groups.google.com/group/django-users.
To contribute to Django:
* Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
information about getting involved.
To run Django's test suite:
* Follow the instructions in the "Unit tests" section of
``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[console_scripts]
django-admin = django.core.management:execute_from_command_line

View File

@ -0,0 +1 @@
{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3 :: Only", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "description_content_type": "UNKNOWN", "extensions": {"python.commands": {"wrap_console": {"django-admin": "django.core.management:execute_from_command_line"}}, "python.details": {"contacts": [{"email": "foundation@djangoproject.com", "name": "Django Software Foundation", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "https://www.djangoproject.com/"}}, "python.exports": {"console_scripts": {"django-admin": "django.core.management:execute_from_command_line"}}}, "extras": ["argon2", "bcrypt"], "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "Django", "project_url": "Documentation, https://docs.djangoproject.com/", "requires_python": ">=3.5", "run_requires": [{"extra": "argon2", "requires": ["argon2-cffi (>=16.1.0)"]}, {"extra": "bcrypt", "requires": ["bcrypt"]}, {"requires": ["pytz"]}], "summary": "A high-level Python Web framework that encourages rapid development and clean, pragmatic design.", "version": "2.1.2"}

View File

@ -0,0 +1 @@
django

View File

@ -0,0 +1,322 @@
PyHamcrest
==========
| |docs| |travis| |coveralls| |landscape| |scrutinizer| |codeclimate|
| |version| |downloads| |wheel| |supported-versions| |supported-implementations|
.. |docs| image:: https://readthedocs.org/projects/pyhamcrest/badge/?style=flat
:target: https://pyhamcrest.readthedocs.org/
:alt: Documentation Status
.. |travis| image:: http://img.shields.io/travis/hamcrest/PyHamcrest/master.png?style=flat
:alt: Travis-CI Build Status
:target: https://travis-ci.org/hamcrest/PyHamcrest
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hamcrest/PyHamcrest?branch=master
:alt: AppVeyor Build Status
:target: https://ci.appveyor.com/project/hamcrest/PyHamcrest
.. |coveralls| image:: http://img.shields.io/coveralls/hamcrest/PyHamcrest/master.png?style=flat
:alt: Coverage Status
:target: https://coveralls.io/r/hamcrest/PyHamcrest
.. |landscape| image:: https://landscape.io/github/hamcrest/PyHamcrest/master/landscape.svg?style=flat
:target: https://landscape.io/github/hamcrest/PyHamcrest/master
:alt: Code Quality Status
.. |codeclimate| image:: https://codeclimate.com/github/hamcrest/PyHamcrest/badges/gpa.svg
:target: https://codeclimate.com/github/hamcrest/PyHamcrest
:alt: Code Climate
.. |version| image:: http://img.shields.io/pypi/v/PyHamcrest.png?style=flat
:alt: PyPI Package latest release
:target: https://pypi.python.org/pypi/PyHamcrest
.. |downloads| image:: http://img.shields.io/pypi/dm/PyHamcrest.png?style=flat
:alt: PyPI Package monthly downloads
:target: https://pypi.python.org/pypi/PyHamcrest
.. |wheel| image:: https://pypip.in/wheel/PyHamcrest/badge.png?style=flat
:alt: PyPI Wheel
:target: https://pypi.python.org/pypi/PyHamcrest
.. |supported-versions| image:: https://pypip.in/py_versions/PyHamcrest/badge.png?style=flat
:alt: Supported versions
:target: https://pypi.python.org/pypi/PyHamcrest
.. |supported-implementations| image:: https://pypip.in/implementation/PyHamcrest/badge.png?style=flat
:alt: Supported imlementations
:target: https://pypi.python.org/pypi/PyHamcrest
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/hamcrest/PyHamcrest/master.png?style=flat
:alt: Scrtinizer Status
:target: https://scrutinizer-ci.com/g/hamcrest/PyHamcrest/
Introduction
============
PyHamcrest is a framework for writing matcher objects, allowing you to
declaratively define "match" rules. There are a number of situations where
matchers are invaluable, such as UI validation, or data filtering, but it is in
the area of writing flexible tests that matchers are most commonly used. This
tutorial shows you how to use PyHamcrest for unit testing.
When writing tests it is sometimes difficult to get the balance right between
overspecifying the test (and making it brittle to changes), and not specifying
enough (making the test less valuable since it continues to pass even when the
thing being tested is broken). Having a tool that allows you to pick out
precisely the aspect under test and describe the values it should have, to a
controlled level of precision, helps greatly in writing tests that are "just
right." Such tests fail when the behavior of the aspect under test deviates
from the expected behavior, yet continue to pass when minor, unrelated changes
to the behaviour are made.
Installation
============
Hamcrest can be installed using the usual Python packaging tools. It depends on
distribute, but as long as you have a network connection when you install, the
installation process will take care of that for you.
My first PyHamcrest test
========================
We'll start by writing a very simple PyUnit test, but instead of using PyUnit's
``assertEqual`` method, we'll use PyHamcrest's ``assert_that`` construct and
the standard set of matchers:
.. code:: python
from hamcrest import *
import unittest
class BiscuitTest(unittest.TestCase):
def testEquals(self):
theBiscuit = Biscuit('Ginger')
myBiscuit = Biscuit('Ginger')
assert_that(theBiscuit, equal_to(myBiscuit))
if __name__ == '__main__':
unittest.main()
The ``assert_that`` function is a stylized sentence for making a test
assertion. In this example, the subject of the assertion is the object
``theBiscuit``, which is the first method parameter. The second method
parameter is a matcher for ``Biscuit`` objects, here a matcher that checks one
object is equal to another using the Python ``==`` operator. The test passes
since the ``Biscuit`` class defines an ``__eq__`` method.
If you have more than one assertion in your test you can include an identifier
for the tested value in the assertion:
.. code:: python
assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), 'chocolate chips')
assert_that(theBiscuit.getHazelnutCount(), equal_to(3), 'hazelnuts')
As a convenience, assert_that can also be used to verify a boolean condition:
.. code:: python
assert_that(theBiscuit.isCooked(), 'cooked')
This is equivalent to the ``assert_`` method of unittest.TestCase, but because
it's a standalone function, it offers greater flexibility in test writing.
Predefined matchers
===================
PyHamcrest comes with a library of useful matchers:
* Object
* ``equal_to`` - match equal object
* ``has_length`` - match ``len()``
* ``has_property`` - match value of property with given name
* ``has_properties`` - match an object that has all of the given properties.
* ``has_string`` - match ``str()``
* ``instance_of`` - match object type
* ``none``, ``not_none`` - match ``None``, or not ``None``
* ``same_instance`` - match same object
* ``calling, raises`` - wrap a method call and assert that it raises an exception
* Number
* ``close_to`` - match number close to a given value
* ``greater_than``, ``greater_than_or_equal_to``, ``less_than``,
``less_than_or_equal_to`` - match numeric ordering
* Text
* ``contains_string`` - match part of a string
* ``ends_with`` - match the end of a string
* ``equal_to_ignoring_case`` - match the complete string but ignore case
* ``equal_to_ignoring_whitespace`` - match the complete string but ignore extra whitespace
* ``matches_regexp`` - match a regular expression in a string
* ``starts_with`` - match the beginning of a string
* ``string_contains_in_order`` - match parts of a string, in relative order
* Logical
* ``all_of`` - ``and`` together all matchers
* ``any_of`` - ``or`` together all matchers
* ``anything`` - match anything, useful in composite matchers when you don't care about a particular value
* ``is_not`` - negate the matcher
* Sequence
* ``contains`` - exactly match the entire sequence
* ``contains_inanyorder`` - match the entire sequence, but in any order
* ``has_item`` - match if given item appears in the sequence
* ``has_items`` - match if all given items appear in the sequence, in any order
* ``is_in`` - match if item appears in the given sequence
* ``only_contains`` - match if sequence's items appear in given list
* ``empty`` - match if the sequence is empty
* Dictionary
* ``has_entries`` - match dictionary with list of key-value pairs
* ``has_entry`` - match dictionary containing a key-value pair
* ``has_key`` - match dictionary with a key
* ``has_value`` - match dictionary with a value
* Decorator
* ``calling`` - wrap a callable in a deffered object, for subsequent matching on calling behaviour
* ``raises`` - Ensure that a deferred callable raises as expected
* ``described_as`` - give the matcher a custom failure description
* ``is_`` - decorator to improve readability - see `Syntactic sugar` below
The arguments for many of these matchers accept not just a matching value, but
another matcher, so matchers can be composed for greater flexibility. For
example, ``only_contains(less_than(5))`` will match any sequence where every
item is less than 5.
Syntactic sugar
===============
PyHamcrest strives to make your tests as readable as possible. For example, the
``is_`` matcher is a wrapper that doesn't add any extra behavior to the
underlying matcher. The following assertions are all equivalent:
.. code:: python
assert_that(theBiscuit, equal_to(myBiscuit))
assert_that(theBiscuit, is_(equal_to(myBiscuit)))
assert_that(theBiscuit, is_(myBiscuit))
The last form is allowed since ``is_(value)`` wraps most non-matcher arguments
with ``equal_to``. But if the argument is a type, it is wrapped with
``instance_of``, so the following are also equivalent:
.. code:: python
assert_that(theBiscuit, instance_of(Biscuit))
assert_that(theBiscuit, is_(instance_of(Biscuit)))
assert_that(theBiscuit, is_(Biscuit))
*Note that PyHamcrest's ``is_`` matcher is unrelated to Python's ``is``
operator. The matcher for object identity is ``same_instance``.*
Writing custom matchers
=======================
PyHamcrest comes bundled with lots of useful matchers, but you'll probably find
that you need to create your own from time to time to fit your testing needs.
This commonly occurs when you find a fragment of code that tests the same set
of properties over and over again (and in different tests), and you want to
bundle the fragment into a single assertion. By writing your own matcher you'll
eliminate code duplication and make your tests more readable!
Let's write our own matcher for testing if a calendar date falls on a Saturday.
This is the test we want to write:
.. code:: python
def testDateIsOnASaturday(self):
d = datetime.date(2008, 04, 26)
assert_that(d, is_(on_a_saturday()))
And here's the implementation:
.. code:: python
from hamcrest.core.base_matcher import BaseMatcher
from hamcrest.core.helpers.hasmethod import hasmethod
class IsGivenDayOfWeek(BaseMatcher):
def __init__(self, day):
self.day = day # Monday is 0, Sunday is 6
def _matches(self, item):
if not hasmethod(item, 'weekday'):
return False
return item.weekday() == self.day
def describe_to(self, description):
day_as_string = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
description.append_text('calendar date falling on ') \
.append_text(day_as_string[self.day])
def on_a_saturday():
return IsGivenDayOfWeek(5)
For our Matcher implementation we implement the ``_matches`` method - which
calls the ``weekday`` method after confirming that the argument (which may not
be a date) has such a method - and the ``describe_to`` method - which is used
to produce a failure message when a test fails. Here's an example of how the
failure message looks:
.. code:: python
assert_that(datetime.date(2008, 04, 06), is_(on_a_saturday()))
fails with the message::
AssertionError:
Expected: is calendar date falling on Saturday
got: <2008-04-06>
Let's say this matcher is saved in a module named ``isgivendayofweek``. We
could use it in our test by importing the factory function ``on_a_saturday``:
.. code:: python
from hamcrest import *
import unittest
from isgivendayofweek import on_a_saturday
class DateTest(unittest.TestCase):
def testDateIsOnASaturday(self):
d = datetime.date(2008, 04, 26)
assert_that(d, is_(on_a_saturday()))
if __name__ == '__main__':
unittest.main()
Even though the ``on_a_saturday`` function creates a new matcher each time it
is called, you should not assume this is the only usage pattern for your
matcher. Therefore you should make sure your matcher is stateless, so a single
instance can be reused between matches.
More resources
==============
* Documentation_
* Package_
* Sources_
* Hamcrest_
.. _Documentation: http://readthedocs.org/docs/pyhamcrest/en/V1.8.2/
.. _Package: http://pypi.python.org/pypi/PyHamcrest
.. _Sources: https://github.com/hamcrest/PyHamcrest
.. _Hamcrest: http://hamcrest.org

View File

@ -0,0 +1,353 @@
Metadata-Version: 2.0
Name: PyHamcrest
Version: 1.9.0
Summary: Hamcrest framework for matcher objects
Home-page: https://github.com/hamcrest/PyHamcrest
Author: Chris Rose
Author-email: offline@offby1.net
License: New BSD
Download-URL: http://pypi.python.org/packages/source/P/PyHamcrest/PyHamcrest-1.9.0.tar.gz
Keywords: hamcrest matchers pyunit unit test testing unittest unittesting
Platform: All
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: Jython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Provides: hamcrest
Requires-Dist: setuptools
Requires-Dist: six
PyHamcrest
==========
| |docs| |travis| |coveralls| |landscape| |scrutinizer| |codeclimate|
| |version| |downloads| |wheel| |supported-versions| |supported-implementations|
.. |docs| image:: https://readthedocs.org/projects/pyhamcrest/badge/?style=flat
:target: https://pyhamcrest.readthedocs.org/
:alt: Documentation Status
.. |travis| image:: http://img.shields.io/travis/hamcrest/PyHamcrest/master.png?style=flat
:alt: Travis-CI Build Status
:target: https://travis-ci.org/hamcrest/PyHamcrest
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hamcrest/PyHamcrest?branch=master
:alt: AppVeyor Build Status
:target: https://ci.appveyor.com/project/hamcrest/PyHamcrest
.. |coveralls| image:: http://img.shields.io/coveralls/hamcrest/PyHamcrest/master.png?style=flat
:alt: Coverage Status
:target: https://coveralls.io/r/hamcrest/PyHamcrest
.. |landscape| image:: https://landscape.io/github/hamcrest/PyHamcrest/master/landscape.svg?style=flat
:target: https://landscape.io/github/hamcrest/PyHamcrest/master
:alt: Code Quality Status
.. |codeclimate| image:: https://codeclimate.com/github/hamcrest/PyHamcrest/badges/gpa.svg
:target: https://codeclimate.com/github/hamcrest/PyHamcrest
:alt: Code Climate
.. |version| image:: http://img.shields.io/pypi/v/PyHamcrest.png?style=flat
:alt: PyPI Package latest release
:target: https://pypi.python.org/pypi/PyHamcrest
.. |downloads| image:: http://img.shields.io/pypi/dm/PyHamcrest.png?style=flat
:alt: PyPI Package monthly downloads
:target: https://pypi.python.org/pypi/PyHamcrest
.. |wheel| image:: https://pypip.in/wheel/PyHamcrest/badge.png?style=flat
:alt: PyPI Wheel
:target: https://pypi.python.org/pypi/PyHamcrest
.. |supported-versions| image:: https://pypip.in/py_versions/PyHamcrest/badge.png?style=flat
:alt: Supported versions
:target: https://pypi.python.org/pypi/PyHamcrest
.. |supported-implementations| image:: https://pypip.in/implementation/PyHamcrest/badge.png?style=flat
:alt: Supported imlementations
:target: https://pypi.python.org/pypi/PyHamcrest
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/hamcrest/PyHamcrest/master.png?style=flat
:alt: Scrtinizer Status
:target: https://scrutinizer-ci.com/g/hamcrest/PyHamcrest/
Introduction
============
PyHamcrest is a framework for writing matcher objects, allowing you to
declaratively define "match" rules. There are a number of situations where
matchers are invaluable, such as UI validation, or data filtering, but it is in
the area of writing flexible tests that matchers are most commonly used. This
tutorial shows you how to use PyHamcrest for unit testing.
When writing tests it is sometimes difficult to get the balance right between
overspecifying the test (and making it brittle to changes), and not specifying
enough (making the test less valuable since it continues to pass even when the
thing being tested is broken). Having a tool that allows you to pick out
precisely the aspect under test and describe the values it should have, to a
controlled level of precision, helps greatly in writing tests that are "just
right." Such tests fail when the behavior of the aspect under test deviates
from the expected behavior, yet continue to pass when minor, unrelated changes
to the behaviour are made.
Installation
============
Hamcrest can be installed using the usual Python packaging tools. It depends on
distribute, but as long as you have a network connection when you install, the
installation process will take care of that for you.
My first PyHamcrest test
========================
We'll start by writing a very simple PyUnit test, but instead of using PyUnit's
``assertEqual`` method, we'll use PyHamcrest's ``assert_that`` construct and
the standard set of matchers:
.. code:: python
from hamcrest import *
import unittest
class BiscuitTest(unittest.TestCase):
def testEquals(self):
theBiscuit = Biscuit('Ginger')
myBiscuit = Biscuit('Ginger')
assert_that(theBiscuit, equal_to(myBiscuit))
if __name__ == '__main__':
unittest.main()
The ``assert_that`` function is a stylized sentence for making a test
assertion. In this example, the subject of the assertion is the object
``theBiscuit``, which is the first method parameter. The second method
parameter is a matcher for ``Biscuit`` objects, here a matcher that checks one
object is equal to another using the Python ``==`` operator. The test passes
since the ``Biscuit`` class defines an ``__eq__`` method.
If you have more than one assertion in your test you can include an identifier
for the tested value in the assertion:
.. code:: python
assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), 'chocolate chips')
assert_that(theBiscuit.getHazelnutCount(), equal_to(3), 'hazelnuts')
As a convenience, assert_that can also be used to verify a boolean condition:
.. code:: python
assert_that(theBiscuit.isCooked(), 'cooked')
This is equivalent to the ``assert_`` method of unittest.TestCase, but because
it's a standalone function, it offers greater flexibility in test writing.
Predefined matchers
===================
PyHamcrest comes with a library of useful matchers:
* Object
* ``equal_to`` - match equal object
* ``has_length`` - match ``len()``
* ``has_property`` - match value of property with given name
* ``has_properties`` - match an object that has all of the given properties.
* ``has_string`` - match ``str()``
* ``instance_of`` - match object type
* ``none``, ``not_none`` - match ``None``, or not ``None``
* ``same_instance`` - match same object
* ``calling, raises`` - wrap a method call and assert that it raises an exception
* Number
* ``close_to`` - match number close to a given value
* ``greater_than``, ``greater_than_or_equal_to``, ``less_than``,
``less_than_or_equal_to`` - match numeric ordering
* Text
* ``contains_string`` - match part of a string
* ``ends_with`` - match the end of a string
* ``equal_to_ignoring_case`` - match the complete string but ignore case
* ``equal_to_ignoring_whitespace`` - match the complete string but ignore extra whitespace
* ``matches_regexp`` - match a regular expression in a string
* ``starts_with`` - match the beginning of a string
* ``string_contains_in_order`` - match parts of a string, in relative order
* Logical
* ``all_of`` - ``and`` together all matchers
* ``any_of`` - ``or`` together all matchers
* ``anything`` - match anything, useful in composite matchers when you don't care about a particular value
* ``is_not`` - negate the matcher
* Sequence
* ``contains`` - exactly match the entire sequence
* ``contains_inanyorder`` - match the entire sequence, but in any order
* ``has_item`` - match if given item appears in the sequence
* ``has_items`` - match if all given items appear in the sequence, in any order
* ``is_in`` - match if item appears in the given sequence
* ``only_contains`` - match if sequence's items appear in given list
* ``empty`` - match if the sequence is empty
* Dictionary
* ``has_entries`` - match dictionary with list of key-value pairs
* ``has_entry`` - match dictionary containing a key-value pair
* ``has_key`` - match dictionary with a key
* ``has_value`` - match dictionary with a value
* Decorator
* ``calling`` - wrap a callable in a deffered object, for subsequent matching on calling behaviour
* ``raises`` - Ensure that a deferred callable raises as expected
* ``described_as`` - give the matcher a custom failure description
* ``is_`` - decorator to improve readability - see `Syntactic sugar` below
The arguments for many of these matchers accept not just a matching value, but
another matcher, so matchers can be composed for greater flexibility. For
example, ``only_contains(less_than(5))`` will match any sequence where every
item is less than 5.
Syntactic sugar
===============
PyHamcrest strives to make your tests as readable as possible. For example, the
``is_`` matcher is a wrapper that doesn't add any extra behavior to the
underlying matcher. The following assertions are all equivalent:
.. code:: python
assert_that(theBiscuit, equal_to(myBiscuit))
assert_that(theBiscuit, is_(equal_to(myBiscuit)))
assert_that(theBiscuit, is_(myBiscuit))
The last form is allowed since ``is_(value)`` wraps most non-matcher arguments
with ``equal_to``. But if the argument is a type, it is wrapped with
``instance_of``, so the following are also equivalent:
.. code:: python
assert_that(theBiscuit, instance_of(Biscuit))
assert_that(theBiscuit, is_(instance_of(Biscuit)))
assert_that(theBiscuit, is_(Biscuit))
*Note that PyHamcrest's ``is_`` matcher is unrelated to Python's ``is``
operator. The matcher for object identity is ``same_instance``.*
Writing custom matchers
=======================
PyHamcrest comes bundled with lots of useful matchers, but you'll probably find
that you need to create your own from time to time to fit your testing needs.
This commonly occurs when you find a fragment of code that tests the same set
of properties over and over again (and in different tests), and you want to
bundle the fragment into a single assertion. By writing your own matcher you'll
eliminate code duplication and make your tests more readable!
Let's write our own matcher for testing if a calendar date falls on a Saturday.
This is the test we want to write:
.. code:: python
def testDateIsOnASaturday(self):
d = datetime.date(2008, 04, 26)
assert_that(d, is_(on_a_saturday()))
And here's the implementation:
.. code:: python
from hamcrest.core.base_matcher import BaseMatcher
from hamcrest.core.helpers.hasmethod import hasmethod
class IsGivenDayOfWeek(BaseMatcher):
def __init__(self, day):
self.day = day # Monday is 0, Sunday is 6
def _matches(self, item):
if not hasmethod(item, 'weekday'):
return False
return item.weekday() == self.day
def describe_to(self, description):
day_as_string = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
description.append_text('calendar date falling on ') \
.append_text(day_as_string[self.day])
def on_a_saturday():
return IsGivenDayOfWeek(5)
For our Matcher implementation we implement the ``_matches`` method - which
calls the ``weekday`` method after confirming that the argument (which may not
be a date) has such a method - and the ``describe_to`` method - which is used
to produce a failure message when a test fails. Here's an example of how the
failure message looks:
.. code:: python
assert_that(datetime.date(2008, 04, 06), is_(on_a_saturday()))
fails with the message::
AssertionError:
Expected: is calendar date falling on Saturday
got: <2008-04-06>
Let's say this matcher is saved in a module named ``isgivendayofweek``. We
could use it in our test by importing the factory function ``on_a_saturday``:
.. code:: python
from hamcrest import *
import unittest
from isgivendayofweek import on_a_saturday
class DateTest(unittest.TestCase):
def testDateIsOnASaturday(self):
d = datetime.date(2008, 04, 26)
assert_that(d, is_(on_a_saturday()))
if __name__ == '__main__':
unittest.main()
Even though the ``on_a_saturday`` function creates a new matcher each time it
is called, you should not assume this is the only usage pattern for your
matcher. Therefore you should make sure your matcher is stateless, so a single
instance can be reused between matches.
More resources
==============
* Documentation_
* Package_
* Sources_
* Hamcrest_
.. _Documentation: http://readthedocs.org/docs/pyhamcrest/en/V1.8.2/
.. _Package: http://pypi.python.org/pypi/PyHamcrest
.. _Sources: https://github.com/hamcrest/PyHamcrest
.. _Hamcrest: http://hamcrest.org

View File

@ -0,0 +1,120 @@
hamcrest/__init__.py,sha256=Uo0mxeePyXP9_yIzBBvKqIL6tzEHqBwBO1eSHfAKies,230
hamcrest/core/__init__.py,sha256=nDkYm1E1P7XpHmR8EHu17eahlmC6uYix4HurTyJjhlE,230
hamcrest/core/assert_that.py,sha256=mPdTQQVsjUliK3xp8l_Vn3YmMzg_h5rYhWJk2mstMc0,2428
hamcrest/core/base_description.py,sha256=auvrelro2vGh_A05TQxwbaBJStvfHiFqm0TL56luxEU,2841
hamcrest/core/base_matcher.py,sha256=eRhLv7zFBJGaz68vEsDlvKbrWwU8GRDfZc7pWBNcxwI,1193
hamcrest/core/compat.py,sha256=wUi1u_nIhgUA3kFGJDZLQ2D_cRw79E1SxPDMaI4mzG0,642
hamcrest/core/description.py,sha256=gbVS-ejZJ783o-WEA66bXnXc9DH5cOlQXg0FlzoVGro,1729
hamcrest/core/matcher.py,sha256=BG6e8bIJvwFI0qkaSkt_SOuuLI0VidvoX8GQLadYssU,1851
hamcrest/core/selfdescribing.py,sha256=RzwqHRGg00AJYweGiP8JzSxoHFdBo0Q7m5axYfutfG8,574
hamcrest/core/selfdescribingvalue.py,sha256=OpGLOjdPA9FSgmLZzmkfYrDvpG261TDDiM56muROFqQ,834
hamcrest/core/string_description.py,sha256=Jp-SbuY8LAwPucL4NMrwWqRhkg6CMwYE-pH0ZDMUq8A,908
hamcrest/core/core/__init__.py,sha256=7q_pNO9cmtV3BEixiGcueNdbxRxlKEFZ1RSO-5GP58E,770
hamcrest/core/core/allof.py,sha256=x2ea18Z5cioRybs1heTd3E8zYtpFbuBJg3vIU3vl9k0,1487
hamcrest/core/core/anyof.py,sha256=8j2KAsaToXbv8CA5hed4bqPR3hscODIiaFhAlAyJ2Vs,1097
hamcrest/core/core/described_as.py,sha256=kyy_qRpoebWB1bS2ReAkNDt-79iXu6HqHxOG0GT7PtQ,1623
hamcrest/core/core/is_.py,sha256=C7UCASfr4AIlaaMZnT5d34OthAZQ3rYv8OhszQjz-Eg,2547
hamcrest/core/core/isanything.py,sha256=D2QO5dbDhwlVRGLFrnxOSijTyeKN2iWLRwjpvnumkvg,798
hamcrest/core/core/isequal.py,sha256=TAwF_lWIjVokPruXN8IGS6ajzPGlThanTLPdhktKwRQ,893
hamcrest/core/core/isinstanceof.py,sha256=dnt8YKLhYwGZvDkJ0OTdLd1PN9cB9ZWCg5k8kdnEuqI,1352
hamcrest/core/core/isnone.py,sha256=51ueQKBgg0RzWVLzjFOyt6K0ejk2EbXUlUX90ORPm80,557
hamcrest/core/core/isnot.py,sha256=_z_ynAVUXEUGWoiRlynFa_yaCVh9cK9LIOznDfKVZeo,1533
hamcrest/core/core/issame.py,sha256=8FvAjud4HTuyz7O-XoJbzLtKhChCr1_5JmzMYMeQt1s,1261
hamcrest/core/core/raises.py,sha256=BZXtMlQiEqEjQMYT76HUiuQNRjTECIUmL82_FKYhAvs,3610
hamcrest/core/helpers/__init__.py,sha256=mPsycYI18LoGawOo9BfETa7yKtnM-fDjFOr43BIevUg,146
hamcrest/core/helpers/hasmethod.py,sha256=LPh_WDRuyKYII3G3fX_x2Ql-ECuPJn4tK5eWMLbetLg,325
hamcrest/core/helpers/wrap_matcher.py,sha256=IQTtw98Pp1NXcVTy9boaNh6jayvawKHhX62R3ZwnVwQ,880
hamcrest/library/__init__.py,sha256=2atNiBCC2g3c-7jw53CltNgU4wEao1uRcheUPl1ML50,1014
hamcrest/library/collection/__init__.py,sha256=iJU6WCsf0R22m11fqMA9Ztb161AZAdrsKG-4Cj38lZ0,635
hamcrest/library/collection/is_empty.py,sha256=p3-B7DCmdbVzqiW3D1h3krdeqmu9B0mfYOaa6HehODg,913
hamcrest/library/collection/isdict_containing.py,sha256=6QxDtDp_Z2TK-6om8cHnJDh45YdmaNHAEy5n97rzf00,2056
hamcrest/library/collection/isdict_containingentries.py,sha256=xKtdFjrwLN32rUdRR4PBUSYa3yACa6jXsmlZv0D9YAU,5168
hamcrest/library/collection/isdict_containingkey.py,sha256=aiBpusjpZmkUEMZ_rUFZBB1GxIfsqluMjhXoWNScqZY,1535
hamcrest/library/collection/isdict_containingvalue.py,sha256=N7mHKgMnd7q6HsQekHH6DShNYbLiSXN9cpQgcdMIjlw,1565
hamcrest/library/collection/isin.py,sha256=bcVslW0fUq0pM_SrT9gdltTlNfeJkrVPZAlg6riV2Ys,774
hamcrest/library/collection/issequence_containing.py,sha256=ZwYMm2-Ul_JtvjcgdMjipYdz82yUbGmRNsdPAjO9RX0,3001
hamcrest/library/collection/issequence_containinginanyorder.py,sha256=Px_W2-_0XDOXiL2NTTMp16ZTZvBl4m5rXjlMoR4Ulmw,3613
hamcrest/library/collection/issequence_containinginorder.py,sha256=x7AT_kOCaPY0LmZw28ln8xLLHBtT-I3NneoCWzMJoYA,3219
hamcrest/library/collection/issequence_onlycontaining.py,sha256=Ia17P1HVgb43lZfdUEhmPyUUUWtGLix__fqXIQJTUiI,1626
hamcrest/library/integration/__init__.py,sha256=3aiupojVacPksKTXVhqRs9OwUDoUlUw-bjWItJnRg8Q,254
hamcrest/library/integration/match_equality.py,sha256=0BMth20YLTqjqTLT4qMVldAe2dQV3CJ2j3zLXDIGl9c,1192
hamcrest/library/number/__init__.py,sha256=J3UoFdR9UPq9zXSKe1a9qAlpjaVst8-pnaxsvbCPj78,335
hamcrest/library/number/iscloseto.py,sha256=2lQTw3Xvo5MW-aePxpWVUOwyV_ydXtH6cCAIAxeopk8,2259
hamcrest/library/number/ordering_comparison.py,sha256=8XxVSOzPK29D14h4wtBZYVYKZf6IcABaQEKihEuzlhI,1700
hamcrest/library/object/__init__.py,sha256=pxzCpybBHRaIg7RJUAw7R1Po0llw8QBbVv_R1TXNBhc,319
hamcrest/library/object/haslength.py,sha256=mpYVvrBZV548FwEeqlHWYofv9LPgChvnypZ4RhZDMp0,1681
hamcrest/library/object/hasproperty.py,sha256=9t8upZxjqSQp6IvGyTm4ftGvcpBeyk0dy36HgvlXZBg,5740
hamcrest/library/object/hasstring.py,sha256=_Ht1x-DwV4hk2fRuGo_KoayoLOIoWObKoA30u7HnABU,1250
hamcrest/library/text/__init__.py,sha256=3Uuy1lY2p0VEUy1SAIO6IZgDDgyx8ZsB98k-J2FA_R0,548
hamcrest/library/text/isequal_ignoring_case.py,sha256=VGkR3PNDOVE1MJT9H4a3SjR2SIYNzT80VoV6NEq3aqw,1257
hamcrest/library/text/isequal_ignoring_whitespace.py,sha256=NQzswc7fk5rOhcoHO4zbYSWqn-WQdQ7Hwf0FoDHAwBM,1667
hamcrest/library/text/stringcontains.py,sha256=JbkSxdFkpRrNYJfSUghboxe1jRLfHAJvOn6PYvMx7fQ,953
hamcrest/library/text/stringcontainsinorder.py,sha256=B3qG7TG24_WyVPGJER2iqi7fzOMZN0UsBRJPtWutBkc,1705
hamcrest/library/text/stringendswith.py,sha256=JjukJWSVWgURvTstrbCGCQdQzY0PMWLul__UlDh2NGA,980
hamcrest/library/text/stringmatches.py,sha256=AEBn8NI3q-YzRUdXiAfnw1Kmse-LLxJUluDAuy_D9nU,1151
hamcrest/library/text/stringstartswith.py,sha256=oOro8G3z8nAOUyOjHHkHhUvY-lt7XRTJzDr9dYxxons,1007
hamcrest/library/text/substringmatcher.py,sha256=lSPxE7pTpTJlUkMtd28WgsM1FFm56JVZixSvAldSZXk,695
PyHamcrest-1.9.0.dist-info/DESCRIPTION.rst,sha256=13XTDh2baR2aJ91v_lAOjEXD7Ydush_RHhk0Z3azfs8,11973
PyHamcrest-1.9.0.dist-info/METADATA,sha256=s0naXZHUZhft0MWcUeZVaZYlREKPOqHp3mUQrhUoC6s,13276
PyHamcrest-1.9.0.dist-info/metadata.json,sha256=Jqs-ZSW5kWSnfvxDsRMwSQH6TxEqSJjBJWiQk1G-c_A,1545
PyHamcrest-1.9.0.dist-info/pbr.json,sha256=jdAcCmfO0nnMs9-YKXuwDyKnxc4qGwyUxyGulF9Pam4,47
PyHamcrest-1.9.0.dist-info/RECORD,,
PyHamcrest-1.9.0.dist-info/top_level.txt,sha256=mRc0yPsPQSqFgWBmZBY33u-05Xtm5M4GEve4NjYdloQ,9
PyHamcrest-1.9.0.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
PyHamcrest-1.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
hamcrest/core/core/__pycache__/described_as.cpython-36.pyc,,
hamcrest/core/core/__pycache__/raises.cpython-36.pyc,,
hamcrest/core/core/__pycache__/is_.cpython-36.pyc,,
hamcrest/core/core/__pycache__/isanything.cpython-36.pyc,,
hamcrest/core/core/__pycache__/isnot.cpython-36.pyc,,
hamcrest/core/core/__pycache__/isinstanceof.cpython-36.pyc,,
hamcrest/core/core/__pycache__/issame.cpython-36.pyc,,
hamcrest/core/core/__pycache__/__init__.cpython-36.pyc,,
hamcrest/core/core/__pycache__/allof.cpython-36.pyc,,
hamcrest/core/core/__pycache__/anyof.cpython-36.pyc,,
hamcrest/core/core/__pycache__/isequal.cpython-36.pyc,,
hamcrest/core/core/__pycache__/isnone.cpython-36.pyc,,
hamcrest/core/__pycache__/assert_that.cpython-36.pyc,,
hamcrest/core/__pycache__/selfdescribing.cpython-36.pyc,,
hamcrest/core/__pycache__/compat.cpython-36.pyc,,
hamcrest/core/__pycache__/description.cpython-36.pyc,,
hamcrest/core/__pycache__/base_matcher.cpython-36.pyc,,
hamcrest/core/__pycache__/matcher.cpython-36.pyc,,
hamcrest/core/__pycache__/__init__.cpython-36.pyc,,
hamcrest/core/__pycache__/string_description.cpython-36.pyc,,
hamcrest/core/__pycache__/base_description.cpython-36.pyc,,
hamcrest/core/__pycache__/selfdescribingvalue.cpython-36.pyc,,
hamcrest/core/helpers/__pycache__/hasmethod.cpython-36.pyc,,
hamcrest/core/helpers/__pycache__/wrap_matcher.cpython-36.pyc,,
hamcrest/core/helpers/__pycache__/__init__.cpython-36.pyc,,
hamcrest/__pycache__/__init__.cpython-36.pyc,,
hamcrest/library/__pycache__/__init__.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/isdict_containingkey.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/isdict_containingvalue.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/isin.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/isdict_containingentries.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/issequence_containinginorder.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/issequence_containinginanyorder.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/issequence_containing.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/is_empty.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/__init__.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/isdict_containing.cpython-36.pyc,,
hamcrest/library/collection/__pycache__/issequence_onlycontaining.cpython-36.pyc,,
hamcrest/library/object/__pycache__/hasstring.cpython-36.pyc,,
hamcrest/library/object/__pycache__/haslength.cpython-36.pyc,,
hamcrest/library/object/__pycache__/__init__.cpython-36.pyc,,
hamcrest/library/object/__pycache__/hasproperty.cpython-36.pyc,,
hamcrest/library/integration/__pycache__/__init__.cpython-36.pyc,,
hamcrest/library/integration/__pycache__/match_equality.cpython-36.pyc,,
hamcrest/library/text/__pycache__/substringmatcher.cpython-36.pyc,,
hamcrest/library/text/__pycache__/stringmatches.cpython-36.pyc,,
hamcrest/library/text/__pycache__/stringstartswith.cpython-36.pyc,,
hamcrest/library/text/__pycache__/stringcontains.cpython-36.pyc,,
hamcrest/library/text/__pycache__/stringendswith.cpython-36.pyc,,
hamcrest/library/text/__pycache__/__init__.cpython-36.pyc,,
hamcrest/library/text/__pycache__/stringcontainsinorder.cpython-36.pyc,,
hamcrest/library/text/__pycache__/isequal_ignoring_whitespace.cpython-36.pyc,,
hamcrest/library/text/__pycache__/isequal_ignoring_case.cpython-36.pyc,,
hamcrest/library/number/__pycache__/iscloseto.cpython-36.pyc,,
hamcrest/library/number/__pycache__/ordering_comparison.cpython-36.pyc,,
hamcrest/library/number/__pycache__/__init__.cpython-36.pyc,,

View 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

View File

@ -0,0 +1 @@
{"license": "New BSD", "download_url": "http://pypi.python.org/packages/source/P/PyHamcrest/PyHamcrest-1.9.0.tar.gz", "name": "PyHamcrest", "provides": "hamcrest", "test_requires": [{"requires": ["hypothesis (>=1.11)", "pytest (>=2.8)", "mock", "pytest-cov"]}], "extensions": {"python.details": {"project_urls": {"Home": "https://github.com/hamcrest/PyHamcrest"}, "contacts": [{"name": "Chris Rose", "role": "author", "email": "offline@offby1.net"}], "document_names": {"description": "DESCRIPTION.rst"}}}, "run_requires": [{"requires": ["setuptools", "six"]}], "generator": "bdist_wheel (0.24.0)", "summary": "Hamcrest framework for matcher objects", "extras": [], "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: Jython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing"], "version": "1.9.0", "metadata_version": "2.0", "keywords": ["hamcrest", "matchers", "pyunit", "unit", "test", "testing", "unittest", "unittesting"], "platform": "All"}

View File

@ -0,0 +1 @@
{"is_release": false, "git_version": "d572d69"}

View File

@ -0,0 +1 @@
hamcrest

View File

@ -0,0 +1,30 @@
Metadata-Version: 2.1
Name: Twisted
Version: 18.9.0
Summary: An asynchronous networking framework written in Python
Home-page: http://twistedmatrix.com/
Author: Twisted Matrix Laboratories
Author-email: twisted-python@twistedmatrix.com
Maintainer: Glyph Lefkowitz
Maintainer-email: glyph@twistedmatrix.com
License: MIT
Description: An extensible framework for Python programming, with special focus
on event-based network programming and multiprotocol integration.
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Provides-Extra: all_non_platform
Provides-Extra: soap
Provides-Extra: macos_platform
Provides-Extra: http2
Provides-Extra: osx_platform
Provides-Extra: conch
Provides-Extra: dev
Provides-Extra: windows_platform
Provides-Extra: tls
Provides-Extra: serial

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
[console_scripts]
cftp = twisted.conch.scripts.cftp:run
ckeygen = twisted.conch.scripts.ckeygen:run
conch = twisted.conch.scripts.conch:run
mailmail = twisted.mail.scripts.mailmail:run
pyhtmlizer = twisted.scripts.htmlizer:run
tkconch = twisted.conch.scripts.tkconch:run
trial = twisted.scripts.trial:run
twist = twisted.application.twist._twist:Twist.main
twistd = twisted.scripts.twistd:run

View File

@ -0,0 +1,89 @@
zope.interface>=4.4.2
constantly>=15.1
incremental>=16.10.1
Automat>=0.3.0
hyperlink>=17.1.1
PyHamcrest>=1.9.0
attrs>=17.4.0
[all_non_platform]
pyopenssl>=16.0.0
service_identity
idna!=2.3,>=0.6
pyasn1
cryptography>=1.5
appdirs>=1.4.0
soappy
pyserial>=3.0
h2<4.0,>=3.0
priority<2.0,>=1.1.0
[conch]
pyasn1
cryptography>=1.5
appdirs>=1.4.0
[dev]
pyflakes>=1.0.0
twisted-dev-tools>=0.0.2
python-subunit
sphinx>=1.3.1
towncrier>=17.4.0
[http2]
h2<4.0,>=3.0
priority<2.0,>=1.1.0
[macos_platform]
pyobjc-core
pyobjc-framework-CFNetwork
pyobjc-framework-Cocoa
pyopenssl>=16.0.0
service_identity
idna!=2.3,>=0.6
pyasn1
cryptography>=1.5
appdirs>=1.4.0
soappy
pyserial>=3.0
h2<4.0,>=3.0
priority<2.0,>=1.1.0
[osx_platform]
pyobjc-core
pyobjc-framework-CFNetwork
pyobjc-framework-Cocoa
pyopenssl>=16.0.0
service_identity
idna!=2.3,>=0.6
pyasn1
cryptography>=1.5
appdirs>=1.4.0
soappy
pyserial>=3.0
h2<4.0,>=3.0
priority<2.0,>=1.1.0
[serial]
pyserial>=3.0
[soap]
soappy
[tls]
pyopenssl>=16.0.0
service_identity
idna!=2.3,>=0.6
[windows_platform]
pywin32
pyopenssl>=16.0.0
service_identity
idna!=2.3,>=0.6
pyasn1
cryptography>=1.5
appdirs>=1.4.0
soappy
pyserial>=3.0
h2<4.0,>=3.0
priority<2.0,>=1.1.0

View File

@ -0,0 +1,737 @@
aioredis
========
asyncio (PEP 3156) Redis client library.
.. image:: https://travis-ci.org/aio-libs/aioredis.svg?branch=master
:target: https://travis-ci.org/aio-libs/aioredis
.. image:: https://codecov.io/gh/aio-libs/aioredis/branch/master/graph/badge.svg
:target: https://codecov.io/gh/aio-libs/aioredis
.. image:: https://ci.appveyor.com/api/projects/status/wngyx6s98o6hsxmt/branch/master?svg=true
:target: https://ci.appveyor.com/project/popravich/aioredis
Features
--------
================================ ==============================
hiredis_ parser Yes
Pure-python parser Yes
Low-level & High-level APIs Yes
Connections Pool Yes
Pipelining support Yes
Pub/Sub support Yes
SSL/TLS support Yes
Sentinel support Yes [1]_
Redis Cluster support WIP
Trollius (python 2.7) No
Tested CPython versions `3.5, 3.6 <travis_>`_ [2]_
Tested PyPy3 versions `5.9.0 <travis_>`_
Tested for Redis server `2.6, 2.8, 3.0, 3.2, 4.0 <travis_>`_
Support for dev Redis server through low-level API
================================ ==============================
.. [1] Sentinel support is available in master branch.
This feature is not yet stable and may have some issues.
.. [2] For Python 3.3, 3.4 support use aioredis v0.3.
Documentation
-------------
http://aioredis.readthedocs.io/
Usage examples
--------------
Simple low-level interface:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
conn = await aioredis.create_connection(
'redis://localhost', loop=loop)
await conn.execute('set', 'my-key', 'value')
val = await conn.execute('get', 'my-key')
print(val)
conn.close()
await conn.wait_closed()
loop.run_until_complete(go())
# will print 'value'
Simple high-level interface:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
redis = await aioredis.create_redis(
'redis://localhost', loop=loop)
await redis.set('my-key', 'value')
val = await redis.get('my-key')
print(val)
redis.close()
await redis.wait_closed()
loop.run_until_complete(go())
# will print 'value'
Connections pool:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
pool = await aioredis.create_pool(
'redis://localhost',
minsize=5, maxsize=10,
loop=loop)
await pool.execute('set', 'my-key', 'value')
print(await pool.execute('get', 'my-key'))
# graceful shutdown
pool.close()
await pool.wait_closed()
loop.run_until_complete(go())
Simple high-level interface with connections pool:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
redis = await aioredis.create_redis_pool(
'redis://localhost',
minsize=5, maxsize=10,
loop=loop)
await redis.set('my-key', 'value')
val = await redis.get('my-key')
print(val)
redis.close()
await redis.wait_closed()
loop.run_until_complete(go())
# will print 'value'
Requirements
------------
* Python_ 3.5.3+
* hiredis_
.. note::
hiredis is preferred requirement.
Pure-python protocol parser is implemented as well and can be used
through ``parser`` parameter.
Benchmarks
----------
Benchmarks can be found here: https://github.com/popravich/python-redis-benchmark
Discussion list
---------------
*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs
Or gitter room: https://gitter.im/aio-libs/Lobby
License
-------
The aioredis is offered under MIT license.
.. _Python: https://www.python.org
.. _hiredis: https://pypi.python.org/pypi/hiredis
.. _travis: https://travis-ci.org/aio-libs/aioredis
Changes
-------
1.1.0 (2018-02-16)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Implement new commands: ``wait``, ``touch``, ``swapdb``, ``unlink``
(see `#376 <https://github.com/aio-libs/aioredis/pull/376>`_);
* Add ``async_op`` argument to ``flushall`` and ``flushdb`` commands
(see `#364 <https://github.com/aio-libs/aioredis/pull/364>`_,
and `#370 <https://github.com/aio-libs/aioredis/pull/370>`_);
**FIX**:
* **Important!** Fix Sentinel sentinel client with pool ``minsize``
greater than 1
(see `#380 <https://github.com/aio-libs/aioredis/pull/380>`_);
* Fix ``SentinelPool.discover_timeout`` usage
(see `#379 <https://github.com/aio-libs/aioredis/pull/379>`_);
* Fix ``Receiver`` hang on disconnect
(see `#354 <https://github.com/aio-libs/aioredis/pull/354>`_,
and `#366 <https://github.com/aio-libs/aioredis/pull/366>`_);
* Fix an issue with ``subscribe``/``psubscribe`` with empty pool
(see `#351 <https://github.com/aio-libs/aioredis/pull/351>`_,
and `#355 <https://github.com/aio-libs/aioredis/pull/355>`_);
* Fix an issue when ``StreamReader``'s feed_data is called before set_parser
(see `#347 <https://github.com/aio-libs/aioredis/pull/347>`_);
**MISC**:
* Update dependencies versions;
* Multiple test fixes;
1.0.0 (2017-11-17)
^^^^^^^^^^^^^^^^^^
**NEW**:
* **Important!** Drop Python 3.3, 3.4 support;
(see `#321 <https://github.com/aio-libs/aioredis/pull/321>`_,
`#323 <https://github.com/aio-libs/aioredis/pull/323>`_
and `#326 <https://github.com/aio-libs/aioredis/pull/326>`_);
* **Important!** Connections pool has been refactored; now ``create_redis``
function will yield ``Redis`` instance instead of ``RedisPool``
(see `#129 <https://github.com/aio-libs/aioredis/pull/129>`_);
* **Important!** Change sorted set commands reply format:
return list of tuples instead of plain list for commands
accepting ``withscores`` argument
(see `#334 <https://github.com/aio-libs/aioredis/pull/334>`_);
* **Important!** Change ``hscan`` command reply format:
return list of tuples instead of mixed key-value list
(see `#335 <https://github.com/aio-libs/aioredis/pull/335>`_);
* Implement Redis URI support as supported ``address`` argument value
(see `#322 <https://github.com/aio-libs/aioredis/pull/322>`_);
* Dropped ``create_reconnecting_redis``, ``create_redis_pool`` should be
used instead;
* Implement custom ``StreamReader``
(see `#273 <https://github.com/aio-libs/aioredis/pull/273>`_);
* Implement Sentinel support
(see `#181 <https://github.com/aio-libs/aioredis/pull/181>`_);
* Implement pure-python parser
(see `#212 <https://github.com/aio-libs/aioredis/pull/212>`_);
* Add ``migrate_keys`` command
(see `#187 <https://github.com/aio-libs/aioredis/pull/187>`_);
* Add ``zrevrangebylex`` command
(see `#201 <https://github.com/aio-libs/aioredis/pull/201>`_);
* Add ``command``, ``command_count``, ``command_getkeys`` and
``command_info`` commands
(see `#229 <https://github.com/aio-libs/aioredis/pull/229>`_);
* Add ``ping`` support in pubsub connection
(see `#264 <https://github.com/aio-libs/aioredis/pull/264>`_);
* Add ``exist`` parameter to ``zadd`` command
(see `#288 <https://github.com/aio-libs/aioredis/pull/288>`_);
* Add ``MaxClientsError`` and implement ``ReplyError`` specialization
(see `#325 <https://github.com/aio-libs/aioredis/pull/325>`_);
* Add ``encoding`` parameter to sorted set commands
(see `#289 <https://github.com/aio-libs/aioredis/pull/289>`_);
**FIX**:
* Fix ``CancelledError`` in ``conn._reader_task``
(see `#301 <https://github.com/aio-libs/aioredis/pull/301>`_);
* Fix pending commands cancellation with ``CancelledError``,
use explicit exception instead of calling ``cancel()`` method
(see `#316 <https://github.com/aio-libs/aioredis/pull/316>`_);
* Correct error message on Sentinel discovery of master/slave with password
(see `#327 <https://github.com/aio-libs/aioredis/pull/327>`_);
* Fix ``bytearray`` support as command argument
(see `#329 <https://github.com/aio-libs/aioredis/pull/329>`_);
* Fix critical bug in patched asyncio.Lock
(see `#256 <https://github.com/aio-libs/aioredis/pull/256>`_);
* Fix Multi/Exec transaction canceled error
(see `#225 <https://github.com/aio-libs/aioredis/pull/225>`_);
* Add missing arguments to ``create_redis`` and ``create_redis_pool``;
* Fix deprecation warning
(see `#191 <https://github.com/aio-libs/aioredis/pull/191>`_);
* Make correct ``__aiter__()``
(see `#192 <https://github.com/aio-libs/aioredis/pull/192>`_);
* Backward compatibility fix for ``with (yield from pool) as conn:``
(see `#205 <https://github.com/aio-libs/aioredis/pull/205>`_);
* Fixed pubsub receiver stop()
(see `#211 <https://github.com/aio-libs/aioredis/pull/211>`_);
**MISC**:
* Multiple test fixes;
* Add PyPy3 to build matrix;
* Update dependencies versions;
* Add missing Python 3.6 classifier;
0.3.5 (2017-11-08)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Fix for indistinguishable futures cancellation with
``asyncio.CancelledError``
(see `#316 <https://github.com/aio-libs/aioredis/pull/316>`_),
cherry-picked from master;
0.3.4 (2017-10-25)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Fix time command result decoding when using connection-wide encoding setting
(see `#266 <https://github.com/aio-libs/aioredis/pull/266>`_);
0.3.3 (2017-06-30)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Critical bug fixed in patched asyncio.Lock
(see `#256 <https://github.com/aio-libs/aioredis/pull/256>`_);
0.3.2 (2017-06-21)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Added ``zrevrangebylex`` command
(see `#201 <https://github.com/aio-libs/aioredis/pull/201>`_),
cherry-picked from master;
* Add connection timeout
(see `#221 <https://github.com/aio-libs/aioredis/pull/221>`_),
cherry-picked from master;
**FIX**:
* Fixed pool close warning
(see `#239 <https://github.com/aio-libs/aioredis/pull/239>`_
and `#236 <https://github.com/aio-libs/aioredis/issues/236>`_),
cherry-picked from master;
* Fixed asyncio Lock deadlock issue
(see `#231 <https://github.com/aio-libs/aioredis/issues/231>`_
and `#241 <https://github.com/aio-libs/aioredis/pull/241>`_);
0.3.1 (2017-05-09)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Fix pubsub Receiver missing iter() method
(see `#203 <https://github.com/aio-libs/aioredis/issues/203>`_);
0.3.0 (2017-01-11)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Pub/Sub connection commands accept ``Channel`` instances
(see `#168 <https://github.com/aio-libs/aioredis/pull/168>`_);
* Implement new Pub/Sub MPSC (multi-producers, single-consumer) Queue --
``aioredis.pubsub.Receiver``
(see `#176 <https://github.com/aio-libs/aioredis/pull/176>`_);
* Add ``aioredis.abc`` module providing abstract base classes
defining interface for basic lib components;
(see `#176 <https://github.com/aio-libs/aioredis/pull/176>`_);
* Implement Geo commands support
(see `#177 <https://github.com/aio-libs/aioredis/pull/177>`_
and `#179 <https://github.com/aio-libs/aioredis/pull/179>`_);
**FIX**:
* Minor tests fixes;
**MISC**:
* Update examples and docs to use ``async``/``await`` syntax
also keeping ``yield from`` examples for history
(see `#173 <https://github.com/aio-libs/aioredis/pull/173>`_);
* Reflow Travis CI configuration; add Python 3.6 section
(see `#170 <https://github.com/aio-libs/aioredis/pull/170>`_);
* Add AppVeyor integration to run tests on Windows
(see `#180 <https://github.com/aio-libs/aioredis/pull/180>`_);
* Update multiple development requirements;
0.2.9 (2016-10-24)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Allow multiple keys in ``EXISTS`` command
(see `#156 <https://github.com/aio-libs/aioredis/issues/156>`_
and `#157 <https://github.com/aio-libs/aioredis/issues/157>`_);
**FIX**:
* Close RedisPool when connection to Redis failed
(see `#136 <https://github.com/aio-libs/aioredis/issues/136>`_);
* Add simple ``INFO`` command argument validation
(see `#140 <https://github.com/aio-libs/aioredis/issues/140>`_);
* Remove invalid uses of ``next()``
**MISC**:
* Update devel.rst docs; update Pub/Sub Channel docs (cross-refs);
* Update MANIFEST.in to include docs, examples and tests in source bundle;
0.2.8 (2016-07-22)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Add ``hmset_dict`` command
(see `#130 <https://github.com/aio-libs/aioredis/issues/130>`_);
* Add ``RedisConnection.address`` property;
* RedisPool ``minsize``/``maxsize`` must not be ``None``;
* Implement ``close()``/``wait_closed()``/``closed`` interface for pool
(see `#128 <https://github.com/aio-libs/aioredis/issues/128>`_);
**FIX**:
* Add test for ``hstrlen``;
* Test fixes
**MISC**:
* Enable Redis 3.2.0 on Travis;
* Add spell checking when building docs
(see `#132 <https://github.com/aio-libs/aioredis/issues/132>`_);
* Documentation updated;
0.2.7 (2016-05-27)
^^^^^^^^^^^^^^^^^^
* ``create_pool()`` minsize default value changed to 1;
* Fixed cancellation of wait_closed
(see `#118 <https://github.com/aio-libs/aioredis/issues/118>`_);
* Fixed ``time()`` convertion to float
(see `#126 <https://github.com/aio-libs/aioredis/issues/126>`_);
* Fixed ``hmset()`` method to return bool instead of ``b'OK'``
(see `#126`_);
* Fixed multi/exec + watch issue (changed watch variable was causing
``tr.execute()`` to fail)
(see `#121 <https://github.com/aio-libs/aioredis/issues/121>`_);
* Replace ``asyncio.Future`` uses with utility method
(get ready to Python 3.5.2 ``loop.create_future()``);
* Tests switched from unittest to pytest (see `#126`_);
* Documentation updates;
0.2.6 (2016-03-30)
^^^^^^^^^^^^^^^^^^
* Fixed Multi/Exec transactions cancellation issue
(see `#110 <https://github.com/aio-libs/aioredis/issues/110>`_
and `#114 <https://github.com/aio-libs/aioredis/issues/114>`_);
* Fixed Pub/Sub subscribe concurrency issue
(see `#113 <https://github.com/aio-libs/aioredis/issues/113>`_
and `#115 <https://github.com/aio-libs/aioredis/issues/115>`_);
* Add SSL/TLS support
(see `#116 <https://github.com/aio-libs/aioredis/issues/116>`_);
* ``aioredis.ConnectionClosedError`` raised in ``execute_pubsub`` as well
(see `#108 <https://github.com/aio-libs/aioredis/issues/108>`_);
* ``Redis.slaveof()`` method signature changed: now to disable
replication one should call ``redis.slaveof(None)`` instead of ``redis.slaveof()``;
* More tests added;
0.2.5 (2016-03-02)
^^^^^^^^^^^^^^^^^^
* Close all Pub/Sub channels on connection close
(see `#88 <https://github.com/aio-libs/aioredis/issues/88>`_);
* Add ``iter()`` method to ``aioredis.Channel`` allowing to use it
with ``async for``
(see `#89 <https://github.com/aio-libs/aioredis/issues/89>`_);
* Inline code samples in docs made runnable and downloadable
(see `#92 <https://github.com/aio-libs/aioredis/issues/92>`_);
* Python 3.5 examples converted to use ``async``/``await`` syntax
(see `#93 <https://github.com/aio-libs/aioredis/issues/93>`_);
* Fix Multi/Exec to honor encoding parameter
(see `#94 <https://github.com/aio-libs/aioredis/issues/94>`_
and `#97 <https://github.com/aio-libs/aioredis/issues/97>`_);
* Add debug message in ``create_connection``
(see `#90 <https://github.com/aio-libs/aioredis/issues/90>`_);
* Replace ``asyncio.async`` calls with wrapper that respects asyncio version
(see `#101 <https://github.com/aio-libs/aioredis/issues/101>`_);
* Use NODELAY option for TCP sockets
(see `#105 <https://github.com/aio-libs/aioredis/issues/105>`_);
* New ``aioredis.ConnectionClosedError`` exception added. Raised if
connection to Redis server is lost
(see `#108 <https://github.com/aio-libs/aioredis/issues/108>`_
and `#109 <https://github.com/aio-libs/aioredis/issues/109>`_);
* Fix RedisPool to close and drop connection in subscribe mode on release;
* Fix ``aioredis.util.decode`` to recursively decode list responses;
* More examples added and docs updated;
* Add google groups link to README;
* Bump year in LICENSE and docs;
0.2.4 (2015-10-13)
^^^^^^^^^^^^^^^^^^
* Python 3.5 ``async`` support:
- New scan commands API (``iscan``, ``izscan``, ``ihscan``);
- Pool made awaitable (allowing ``with await pool: ...`` and ``async
with pool.get() as conn:`` constructs);
* Fixed dropping closed connections from free pool
(see `#83 <https://github.com/aio-libs/aioredis/issues/83>`_);
* Docs updated;
0.2.3 (2015-08-14)
^^^^^^^^^^^^^^^^^^
* Redis cluster support work in progress;
* Fixed pool issue causing pool growth over max size & ``acquire`` call hangs
(see `#71 <https://github.com/aio-libs/aioredis/issues/71>`_);
* ``info`` server command result parsing implemented;
* Fixed behavior of util functions
(see `#70 <https://github.com/aio-libs/aioredis/issues/70>`_);
* ``hstrlen`` command added;
* Few fixes in examples;
* Few fixes in documentation;
0.2.2 (2015-07-07)
^^^^^^^^^^^^^^^^^^
* Decoding data with ``encoding`` parameter now takes into account
list (array) replies
(see `#68 <https://github.com/aio-libs/aioredis/pull/68>`_);
* ``encoding`` parameter added to following commands:
- generic commands: keys, randomkey;
- hash commands: hgetall, hkeys, hmget, hvals;
- list commands: blpop, brpop, brpoplpush, lindex, lpop, lrange, rpop, rpoplpush;
- set commands: smembers, spop, srandmember;
- string commands: getrange, getset, mget;
* Backward incompatibility:
``ltrim`` command now returns bool value instead of 'OK';
* Tests updated;
0.2.1 (2015-07-06)
^^^^^^^^^^^^^^^^^^
* Logging added (aioredis.log module);
* Fixed issue with ``wait_message`` in pub/sub
(see `#66 <https://github.com/aio-libs/aioredis/issues/66>`_);
0.2.0 (2015-06-04)
^^^^^^^^^^^^^^^^^^
* Pub/Sub support added;
* Fix in ``zrevrangebyscore`` command
(see `#62 <https://github.com/aio-libs/aioredis/pull/62>`_);
* Fixes/tests/docs;
0.1.5 (2014-12-09)
^^^^^^^^^^^^^^^^^^
* AutoConnector added;
* wait_closed method added for clean connections shutdown;
* ``zscore`` command fixed;
* Test fixes;
0.1.4 (2014-09-22)
^^^^^^^^^^^^^^^^^^
* Dropped following Redis methods -- ``Redis.multi()``,
``Redis.exec()``, ``Redis.discard()``;
* ``Redis.multi_exec`` hack'ish property removed;
* ``Redis.multi_exec()`` method added;
* High-level commands implemented:
* generic commands (tests);
* transactions commands (api stabilization).
* Backward incompatibilities:
* Following sorted set commands' API changed:
``zcount``, ``zrangebyscore``, ``zremrangebyscore``, ``zrevrangebyscore``;
* set string command' API changed;
0.1.3 (2014-08-08)
^^^^^^^^^^^^^^^^^^
* RedisConnection.execute refactored to support commands pipelining
(see `#33 <http://github.com/aio-libs/aioredis/issues/33>`_);
* Several fixes;
* WIP on transactions and commands interface;
* High-level commands implemented and tested:
* hash commands;
* hyperloglog commands;
* set commands;
* scripting commands;
* string commands;
* list commands;
0.1.2 (2014-07-31)
^^^^^^^^^^^^^^^^^^
* ``create_connection``, ``create_pool``, ``create_redis`` functions updated:
db and password arguments made keyword-only
(see `#26 <http://github.com/aio-libs/aioredis/issues/26>`_);
* Fixed transaction handling
(see `#32 <http://github.com/aio-libs/aioredis/issues/32>`_);
* Response decoding
(see `#16 <http://github.com/aio-libs/aioredis/issues/16>`_);
0.1.1 (2014-07-07)
^^^^^^^^^^^^^^^^^^
* Transactions support (in connection, high-level commands have some issues);
* Docs & tests updated.
0.1.0 (2014-06-24)
^^^^^^^^^^^^^^^^^^
* Initial release;
* RedisConnection implemented;
* RedisPool implemented;
* Docs for RedisConnection & RedisPool;
* WIP on high-level API.

View File

@ -0,0 +1,763 @@
Metadata-Version: 2.0
Name: aioredis
Version: 1.1.0
Summary: asyncio (PEP 3156) Redis support
Home-page: https://github.com/aio-libs/aioredis
Author: Alexey Popravka
Author-email: alexey.popravka@horsedevel.com
License: MIT
Description-Content-Type: UNKNOWN
Platform: POSIX
Classifier: License :: OSI Approved :: MIT License
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: POSIX
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Framework :: AsyncIO
Requires-Dist: async-timeout
Requires-Dist: hiredis
aioredis
========
asyncio (PEP 3156) Redis client library.
.. image:: https://travis-ci.org/aio-libs/aioredis.svg?branch=master
:target: https://travis-ci.org/aio-libs/aioredis
.. image:: https://codecov.io/gh/aio-libs/aioredis/branch/master/graph/badge.svg
:target: https://codecov.io/gh/aio-libs/aioredis
.. image:: https://ci.appveyor.com/api/projects/status/wngyx6s98o6hsxmt/branch/master?svg=true
:target: https://ci.appveyor.com/project/popravich/aioredis
Features
--------
================================ ==============================
hiredis_ parser Yes
Pure-python parser Yes
Low-level & High-level APIs Yes
Connections Pool Yes
Pipelining support Yes
Pub/Sub support Yes
SSL/TLS support Yes
Sentinel support Yes [1]_
Redis Cluster support WIP
Trollius (python 2.7) No
Tested CPython versions `3.5, 3.6 <travis_>`_ [2]_
Tested PyPy3 versions `5.9.0 <travis_>`_
Tested for Redis server `2.6, 2.8, 3.0, 3.2, 4.0 <travis_>`_
Support for dev Redis server through low-level API
================================ ==============================
.. [1] Sentinel support is available in master branch.
This feature is not yet stable and may have some issues.
.. [2] For Python 3.3, 3.4 support use aioredis v0.3.
Documentation
-------------
http://aioredis.readthedocs.io/
Usage examples
--------------
Simple low-level interface:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
conn = await aioredis.create_connection(
'redis://localhost', loop=loop)
await conn.execute('set', 'my-key', 'value')
val = await conn.execute('get', 'my-key')
print(val)
conn.close()
await conn.wait_closed()
loop.run_until_complete(go())
# will print 'value'
Simple high-level interface:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
redis = await aioredis.create_redis(
'redis://localhost', loop=loop)
await redis.set('my-key', 'value')
val = await redis.get('my-key')
print(val)
redis.close()
await redis.wait_closed()
loop.run_until_complete(go())
# will print 'value'
Connections pool:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
pool = await aioredis.create_pool(
'redis://localhost',
minsize=5, maxsize=10,
loop=loop)
await pool.execute('set', 'my-key', 'value')
print(await pool.execute('get', 'my-key'))
# graceful shutdown
pool.close()
await pool.wait_closed()
loop.run_until_complete(go())
Simple high-level interface with connections pool:
.. code:: python
import asyncio
import aioredis
loop = asyncio.get_event_loop()
async def go():
redis = await aioredis.create_redis_pool(
'redis://localhost',
minsize=5, maxsize=10,
loop=loop)
await redis.set('my-key', 'value')
val = await redis.get('my-key')
print(val)
redis.close()
await redis.wait_closed()
loop.run_until_complete(go())
# will print 'value'
Requirements
------------
* Python_ 3.5.3+
* hiredis_
.. note::
hiredis is preferred requirement.
Pure-python protocol parser is implemented as well and can be used
through ``parser`` parameter.
Benchmarks
----------
Benchmarks can be found here: https://github.com/popravich/python-redis-benchmark
Discussion list
---------------
*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs
Or gitter room: https://gitter.im/aio-libs/Lobby
License
-------
The aioredis is offered under MIT license.
.. _Python: https://www.python.org
.. _hiredis: https://pypi.python.org/pypi/hiredis
.. _travis: https://travis-ci.org/aio-libs/aioredis
Changes
-------
1.1.0 (2018-02-16)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Implement new commands: ``wait``, ``touch``, ``swapdb``, ``unlink``
(see `#376 <https://github.com/aio-libs/aioredis/pull/376>`_);
* Add ``async_op`` argument to ``flushall`` and ``flushdb`` commands
(see `#364 <https://github.com/aio-libs/aioredis/pull/364>`_,
and `#370 <https://github.com/aio-libs/aioredis/pull/370>`_);
**FIX**:
* **Important!** Fix Sentinel sentinel client with pool ``minsize``
greater than 1
(see `#380 <https://github.com/aio-libs/aioredis/pull/380>`_);
* Fix ``SentinelPool.discover_timeout`` usage
(see `#379 <https://github.com/aio-libs/aioredis/pull/379>`_);
* Fix ``Receiver`` hang on disconnect
(see `#354 <https://github.com/aio-libs/aioredis/pull/354>`_,
and `#366 <https://github.com/aio-libs/aioredis/pull/366>`_);
* Fix an issue with ``subscribe``/``psubscribe`` with empty pool
(see `#351 <https://github.com/aio-libs/aioredis/pull/351>`_,
and `#355 <https://github.com/aio-libs/aioredis/pull/355>`_);
* Fix an issue when ``StreamReader``'s feed_data is called before set_parser
(see `#347 <https://github.com/aio-libs/aioredis/pull/347>`_);
**MISC**:
* Update dependencies versions;
* Multiple test fixes;
1.0.0 (2017-11-17)
^^^^^^^^^^^^^^^^^^
**NEW**:
* **Important!** Drop Python 3.3, 3.4 support;
(see `#321 <https://github.com/aio-libs/aioredis/pull/321>`_,
`#323 <https://github.com/aio-libs/aioredis/pull/323>`_
and `#326 <https://github.com/aio-libs/aioredis/pull/326>`_);
* **Important!** Connections pool has been refactored; now ``create_redis``
function will yield ``Redis`` instance instead of ``RedisPool``
(see `#129 <https://github.com/aio-libs/aioredis/pull/129>`_);
* **Important!** Change sorted set commands reply format:
return list of tuples instead of plain list for commands
accepting ``withscores`` argument
(see `#334 <https://github.com/aio-libs/aioredis/pull/334>`_);
* **Important!** Change ``hscan`` command reply format:
return list of tuples instead of mixed key-value list
(see `#335 <https://github.com/aio-libs/aioredis/pull/335>`_);
* Implement Redis URI support as supported ``address`` argument value
(see `#322 <https://github.com/aio-libs/aioredis/pull/322>`_);
* Dropped ``create_reconnecting_redis``, ``create_redis_pool`` should be
used instead;
* Implement custom ``StreamReader``
(see `#273 <https://github.com/aio-libs/aioredis/pull/273>`_);
* Implement Sentinel support
(see `#181 <https://github.com/aio-libs/aioredis/pull/181>`_);
* Implement pure-python parser
(see `#212 <https://github.com/aio-libs/aioredis/pull/212>`_);
* Add ``migrate_keys`` command
(see `#187 <https://github.com/aio-libs/aioredis/pull/187>`_);
* Add ``zrevrangebylex`` command
(see `#201 <https://github.com/aio-libs/aioredis/pull/201>`_);
* Add ``command``, ``command_count``, ``command_getkeys`` and
``command_info`` commands
(see `#229 <https://github.com/aio-libs/aioredis/pull/229>`_);
* Add ``ping`` support in pubsub connection
(see `#264 <https://github.com/aio-libs/aioredis/pull/264>`_);
* Add ``exist`` parameter to ``zadd`` command
(see `#288 <https://github.com/aio-libs/aioredis/pull/288>`_);
* Add ``MaxClientsError`` and implement ``ReplyError`` specialization
(see `#325 <https://github.com/aio-libs/aioredis/pull/325>`_);
* Add ``encoding`` parameter to sorted set commands
(see `#289 <https://github.com/aio-libs/aioredis/pull/289>`_);
**FIX**:
* Fix ``CancelledError`` in ``conn._reader_task``
(see `#301 <https://github.com/aio-libs/aioredis/pull/301>`_);
* Fix pending commands cancellation with ``CancelledError``,
use explicit exception instead of calling ``cancel()`` method
(see `#316 <https://github.com/aio-libs/aioredis/pull/316>`_);
* Correct error message on Sentinel discovery of master/slave with password
(see `#327 <https://github.com/aio-libs/aioredis/pull/327>`_);
* Fix ``bytearray`` support as command argument
(see `#329 <https://github.com/aio-libs/aioredis/pull/329>`_);
* Fix critical bug in patched asyncio.Lock
(see `#256 <https://github.com/aio-libs/aioredis/pull/256>`_);
* Fix Multi/Exec transaction canceled error
(see `#225 <https://github.com/aio-libs/aioredis/pull/225>`_);
* Add missing arguments to ``create_redis`` and ``create_redis_pool``;
* Fix deprecation warning
(see `#191 <https://github.com/aio-libs/aioredis/pull/191>`_);
* Make correct ``__aiter__()``
(see `#192 <https://github.com/aio-libs/aioredis/pull/192>`_);
* Backward compatibility fix for ``with (yield from pool) as conn:``
(see `#205 <https://github.com/aio-libs/aioredis/pull/205>`_);
* Fixed pubsub receiver stop()
(see `#211 <https://github.com/aio-libs/aioredis/pull/211>`_);
**MISC**:
* Multiple test fixes;
* Add PyPy3 to build matrix;
* Update dependencies versions;
* Add missing Python 3.6 classifier;
0.3.5 (2017-11-08)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Fix for indistinguishable futures cancellation with
``asyncio.CancelledError``
(see `#316 <https://github.com/aio-libs/aioredis/pull/316>`_),
cherry-picked from master;
0.3.4 (2017-10-25)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Fix time command result decoding when using connection-wide encoding setting
(see `#266 <https://github.com/aio-libs/aioredis/pull/266>`_);
0.3.3 (2017-06-30)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Critical bug fixed in patched asyncio.Lock
(see `#256 <https://github.com/aio-libs/aioredis/pull/256>`_);
0.3.2 (2017-06-21)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Added ``zrevrangebylex`` command
(see `#201 <https://github.com/aio-libs/aioredis/pull/201>`_),
cherry-picked from master;
* Add connection timeout
(see `#221 <https://github.com/aio-libs/aioredis/pull/221>`_),
cherry-picked from master;
**FIX**:
* Fixed pool close warning
(see `#239 <https://github.com/aio-libs/aioredis/pull/239>`_
and `#236 <https://github.com/aio-libs/aioredis/issues/236>`_),
cherry-picked from master;
* Fixed asyncio Lock deadlock issue
(see `#231 <https://github.com/aio-libs/aioredis/issues/231>`_
and `#241 <https://github.com/aio-libs/aioredis/pull/241>`_);
0.3.1 (2017-05-09)
^^^^^^^^^^^^^^^^^^
**FIX**:
* Fix pubsub Receiver missing iter() method
(see `#203 <https://github.com/aio-libs/aioredis/issues/203>`_);
0.3.0 (2017-01-11)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Pub/Sub connection commands accept ``Channel`` instances
(see `#168 <https://github.com/aio-libs/aioredis/pull/168>`_);
* Implement new Pub/Sub MPSC (multi-producers, single-consumer) Queue --
``aioredis.pubsub.Receiver``
(see `#176 <https://github.com/aio-libs/aioredis/pull/176>`_);
* Add ``aioredis.abc`` module providing abstract base classes
defining interface for basic lib components;
(see `#176 <https://github.com/aio-libs/aioredis/pull/176>`_);
* Implement Geo commands support
(see `#177 <https://github.com/aio-libs/aioredis/pull/177>`_
and `#179 <https://github.com/aio-libs/aioredis/pull/179>`_);
**FIX**:
* Minor tests fixes;
**MISC**:
* Update examples and docs to use ``async``/``await`` syntax
also keeping ``yield from`` examples for history
(see `#173 <https://github.com/aio-libs/aioredis/pull/173>`_);
* Reflow Travis CI configuration; add Python 3.6 section
(see `#170 <https://github.com/aio-libs/aioredis/pull/170>`_);
* Add AppVeyor integration to run tests on Windows
(see `#180 <https://github.com/aio-libs/aioredis/pull/180>`_);
* Update multiple development requirements;
0.2.9 (2016-10-24)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Allow multiple keys in ``EXISTS`` command
(see `#156 <https://github.com/aio-libs/aioredis/issues/156>`_
and `#157 <https://github.com/aio-libs/aioredis/issues/157>`_);
**FIX**:
* Close RedisPool when connection to Redis failed
(see `#136 <https://github.com/aio-libs/aioredis/issues/136>`_);
* Add simple ``INFO`` command argument validation
(see `#140 <https://github.com/aio-libs/aioredis/issues/140>`_);
* Remove invalid uses of ``next()``
**MISC**:
* Update devel.rst docs; update Pub/Sub Channel docs (cross-refs);
* Update MANIFEST.in to include docs, examples and tests in source bundle;
0.2.8 (2016-07-22)
^^^^^^^^^^^^^^^^^^
**NEW**:
* Add ``hmset_dict`` command
(see `#130 <https://github.com/aio-libs/aioredis/issues/130>`_);
* Add ``RedisConnection.address`` property;
* RedisPool ``minsize``/``maxsize`` must not be ``None``;
* Implement ``close()``/``wait_closed()``/``closed`` interface for pool
(see `#128 <https://github.com/aio-libs/aioredis/issues/128>`_);
**FIX**:
* Add test for ``hstrlen``;
* Test fixes
**MISC**:
* Enable Redis 3.2.0 on Travis;
* Add spell checking when building docs
(see `#132 <https://github.com/aio-libs/aioredis/issues/132>`_);
* Documentation updated;
0.2.7 (2016-05-27)
^^^^^^^^^^^^^^^^^^
* ``create_pool()`` minsize default value changed to 1;
* Fixed cancellation of wait_closed
(see `#118 <https://github.com/aio-libs/aioredis/issues/118>`_);
* Fixed ``time()`` convertion to float
(see `#126 <https://github.com/aio-libs/aioredis/issues/126>`_);
* Fixed ``hmset()`` method to return bool instead of ``b'OK'``
(see `#126`_);
* Fixed multi/exec + watch issue (changed watch variable was causing
``tr.execute()`` to fail)
(see `#121 <https://github.com/aio-libs/aioredis/issues/121>`_);
* Replace ``asyncio.Future`` uses with utility method
(get ready to Python 3.5.2 ``loop.create_future()``);
* Tests switched from unittest to pytest (see `#126`_);
* Documentation updates;
0.2.6 (2016-03-30)
^^^^^^^^^^^^^^^^^^
* Fixed Multi/Exec transactions cancellation issue
(see `#110 <https://github.com/aio-libs/aioredis/issues/110>`_
and `#114 <https://github.com/aio-libs/aioredis/issues/114>`_);
* Fixed Pub/Sub subscribe concurrency issue
(see `#113 <https://github.com/aio-libs/aioredis/issues/113>`_
and `#115 <https://github.com/aio-libs/aioredis/issues/115>`_);
* Add SSL/TLS support
(see `#116 <https://github.com/aio-libs/aioredis/issues/116>`_);
* ``aioredis.ConnectionClosedError`` raised in ``execute_pubsub`` as well
(see `#108 <https://github.com/aio-libs/aioredis/issues/108>`_);
* ``Redis.slaveof()`` method signature changed: now to disable
replication one should call ``redis.slaveof(None)`` instead of ``redis.slaveof()``;
* More tests added;
0.2.5 (2016-03-02)
^^^^^^^^^^^^^^^^^^
* Close all Pub/Sub channels on connection close
(see `#88 <https://github.com/aio-libs/aioredis/issues/88>`_);
* Add ``iter()`` method to ``aioredis.Channel`` allowing to use it
with ``async for``
(see `#89 <https://github.com/aio-libs/aioredis/issues/89>`_);
* Inline code samples in docs made runnable and downloadable
(see `#92 <https://github.com/aio-libs/aioredis/issues/92>`_);
* Python 3.5 examples converted to use ``async``/``await`` syntax
(see `#93 <https://github.com/aio-libs/aioredis/issues/93>`_);
* Fix Multi/Exec to honor encoding parameter
(see `#94 <https://github.com/aio-libs/aioredis/issues/94>`_
and `#97 <https://github.com/aio-libs/aioredis/issues/97>`_);
* Add debug message in ``create_connection``
(see `#90 <https://github.com/aio-libs/aioredis/issues/90>`_);
* Replace ``asyncio.async`` calls with wrapper that respects asyncio version
(see `#101 <https://github.com/aio-libs/aioredis/issues/101>`_);
* Use NODELAY option for TCP sockets
(see `#105 <https://github.com/aio-libs/aioredis/issues/105>`_);
* New ``aioredis.ConnectionClosedError`` exception added. Raised if
connection to Redis server is lost
(see `#108 <https://github.com/aio-libs/aioredis/issues/108>`_
and `#109 <https://github.com/aio-libs/aioredis/issues/109>`_);
* Fix RedisPool to close and drop connection in subscribe mode on release;
* Fix ``aioredis.util.decode`` to recursively decode list responses;
* More examples added and docs updated;
* Add google groups link to README;
* Bump year in LICENSE and docs;
0.2.4 (2015-10-13)
^^^^^^^^^^^^^^^^^^
* Python 3.5 ``async`` support:
- New scan commands API (``iscan``, ``izscan``, ``ihscan``);
- Pool made awaitable (allowing ``with await pool: ...`` and ``async
with pool.get() as conn:`` constructs);
* Fixed dropping closed connections from free pool
(see `#83 <https://github.com/aio-libs/aioredis/issues/83>`_);
* Docs updated;
0.2.3 (2015-08-14)
^^^^^^^^^^^^^^^^^^
* Redis cluster support work in progress;
* Fixed pool issue causing pool growth over max size & ``acquire`` call hangs
(see `#71 <https://github.com/aio-libs/aioredis/issues/71>`_);
* ``info`` server command result parsing implemented;
* Fixed behavior of util functions
(see `#70 <https://github.com/aio-libs/aioredis/issues/70>`_);
* ``hstrlen`` command added;
* Few fixes in examples;
* Few fixes in documentation;
0.2.2 (2015-07-07)
^^^^^^^^^^^^^^^^^^
* Decoding data with ``encoding`` parameter now takes into account
list (array) replies
(see `#68 <https://github.com/aio-libs/aioredis/pull/68>`_);
* ``encoding`` parameter added to following commands:
- generic commands: keys, randomkey;
- hash commands: hgetall, hkeys, hmget, hvals;
- list commands: blpop, brpop, brpoplpush, lindex, lpop, lrange, rpop, rpoplpush;
- set commands: smembers, spop, srandmember;
- string commands: getrange, getset, mget;
* Backward incompatibility:
``ltrim`` command now returns bool value instead of 'OK';
* Tests updated;
0.2.1 (2015-07-06)
^^^^^^^^^^^^^^^^^^
* Logging added (aioredis.log module);
* Fixed issue with ``wait_message`` in pub/sub
(see `#66 <https://github.com/aio-libs/aioredis/issues/66>`_);
0.2.0 (2015-06-04)
^^^^^^^^^^^^^^^^^^
* Pub/Sub support added;
* Fix in ``zrevrangebyscore`` command
(see `#62 <https://github.com/aio-libs/aioredis/pull/62>`_);
* Fixes/tests/docs;
0.1.5 (2014-12-09)
^^^^^^^^^^^^^^^^^^
* AutoConnector added;
* wait_closed method added for clean connections shutdown;
* ``zscore`` command fixed;
* Test fixes;
0.1.4 (2014-09-22)
^^^^^^^^^^^^^^^^^^
* Dropped following Redis methods -- ``Redis.multi()``,
``Redis.exec()``, ``Redis.discard()``;
* ``Redis.multi_exec`` hack'ish property removed;
* ``Redis.multi_exec()`` method added;
* High-level commands implemented:
* generic commands (tests);
* transactions commands (api stabilization).
* Backward incompatibilities:
* Following sorted set commands' API changed:
``zcount``, ``zrangebyscore``, ``zremrangebyscore``, ``zrevrangebyscore``;
* set string command' API changed;
0.1.3 (2014-08-08)
^^^^^^^^^^^^^^^^^^
* RedisConnection.execute refactored to support commands pipelining
(see `#33 <http://github.com/aio-libs/aioredis/issues/33>`_);
* Several fixes;
* WIP on transactions and commands interface;
* High-level commands implemented and tested:
* hash commands;
* hyperloglog commands;
* set commands;
* scripting commands;
* string commands;
* list commands;
0.1.2 (2014-07-31)
^^^^^^^^^^^^^^^^^^
* ``create_connection``, ``create_pool``, ``create_redis`` functions updated:
db and password arguments made keyword-only
(see `#26 <http://github.com/aio-libs/aioredis/issues/26>`_);
* Fixed transaction handling
(see `#32 <http://github.com/aio-libs/aioredis/issues/32>`_);
* Response decoding
(see `#16 <http://github.com/aio-libs/aioredis/issues/16>`_);
0.1.1 (2014-07-07)
^^^^^^^^^^^^^^^^^^
* Transactions support (in connection, high-level commands have some issues);
* Docs & tests updated.
0.1.0 (2014-06-24)
^^^^^^^^^^^^^^^^^^
* Initial release;
* RedisConnection implemented;
* RedisPool implemented;
* Docs for RedisConnection & RedisPool;
* WIP on high-level API.

View File

@ -0,0 +1,63 @@
aioredis/__init__.py,sha256=loTokM9dPbVAobRSfZhzBuqtu7Uk2WPFcMa8L2estGQ,1404
aioredis/abc.py,sha256=jQOgnK9-DwQbg3maMWdI-FXha_0XCgHQ4iRCJds8EBE,3842
aioredis/connection.py,sha256=wbdG1CiBskC4uNHJqNquPFAY9NQ0i3H8Ij-zsErCUkE,18778
aioredis/errors.py,sha256=UUpPJdFIxDAOh-7KFuxSaWLcDXI0XoJGz6dDffcFS6E,2529
aioredis/locks.py,sha256=N6wQFOaCpNIEf-Yz56aBAlWNqbHRgfvUx8rLx4L8aJQ,1340
aioredis/log.py,sha256=G6I-ADb06gnG0zYdTISUYuwnmpBSfivMfKYIS4r0jAY,423
aioredis/parser.py,sha256=VEP-MSxrY5NuUvkc9l0wWNWm8_VuQQ-Aqsl49nn5mlY,4907
aioredis/pool.py,sha256=7j0_E7Ore1IkZkkS5Ra2-INX57ClUUL9qLvQJTI1Uls,16871
aioredis/pubsub.py,sha256=yean7CCQzo2KCaDkdYCgGDiOXC7NuzOMR4h_3shzCZU,13785
aioredis/stream.py,sha256=_LXLc_IxOhgT93IDTmVxjRYCsBh0YbIQP8v4rTgf9Tw,3243
aioredis/util.py,sha256=ZpyrIJcweW0eetikOlFTNy_8L5FJazXhJXvrjqlyxKU,6070
aioredis/commands/__init__.py,sha256=0zuxax6xd5A5XY9E_A_XAsSVaMgJAeTCOffzo-xFIXk,6721
aioredis/commands/cluster.py,sha256=oBOdLaEFbGOnravvbf7g63FQOub80-U-9XC0fiEnUew,3781
aioredis/commands/generic.py,sha256=9ghaUtqPCu5rCNsH2deZZCwR2l4wmtwbySptkBI7c2Y,11140
aioredis/commands/geo.py,sha256=bkQQAhd_p4wGrtYg6ebRhA70_DZ3ZnkV0J9TspiFLFY,6678
aioredis/commands/hash.py,sha256=eVXcGQE9cy_6jGkIfHLHd6EdgiiBa0RqmFYgTGjl-mM,5223
aioredis/commands/hyperloglog.py,sha256=h8ELJWNhfXR9WqknrydiDM27itLmKY1aX0rlKQrPWmk,782
aioredis/commands/list.py,sha256=kA5qQMxMkgnKPyvoFyPj7KrIHgpTKhHEXrcrQJ8dnd0,5883
aioredis/commands/pubsub.py,sha256=B0btFjGqtdYqLwcXNfsInZaCIJ0RLmunZJrOwMO-Mag,3535
aioredis/commands/scripting.py,sha256=023Ju9r2iwelLDm5ZN_Nw-tNteGqX2jFHxyJqGdmpxE,1187
aioredis/commands/server.py,sha256=E2NbfcxjMi29tLt9F2wIhjeda_TBFK7PhxOLbzbYmbs,10261
aioredis/commands/set.py,sha256=Y2J9ZS6KrYi-VFbpsEIzsVOv_o9jHKgBh-K_VDDJHTU,3283
aioredis/commands/sorted_set.py,sha256=citivjbJgxWDCn7e13ddX9JKsM8gotxgrdVfu4Y8tzk,18016
aioredis/commands/string.py,sha256=ieNfufxcqoTriI8yjob0ZsOTGYf56yY-tIksuRvajT8,8860
aioredis/commands/transaction.py,sha256=Pc8CBE5XABUS6_4UYx0nP9PTc5OsCwKfBKNLk_JnsXI,9955
aioredis/sentinel/__init__.py,sha256=34U1hx-MzBaeEcdIZhBzS9RZ_l08TY5TfRR03ok_ask,213
aioredis/sentinel/commands.py,sha256=FP3hkQ1N79rm1geqS3lsjvr1Q2O2EHRvO5Wswej6VgQ,6182
aioredis/sentinel/pool.py,sha256=3taJWdQBw2PCpeAv43SRVGrGsb7OljvNUnOV9vQDb64,18181
aioredis-1.1.0.dist-info/DESCRIPTION.rst,sha256=WYO-FwNnpE8wK_rMip1Lmy0EklofuBOTqVH_TSRhG-g,19813
aioredis-1.1.0.dist-info/METADATA,sha256=5ztXy3v5nqAFMc_z20WYnDTSOfHtQETv9lwNa80ROqg,20736
aioredis-1.1.0.dist-info/RECORD,,
aioredis-1.1.0.dist-info/WHEEL,sha256=8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA,92
aioredis-1.1.0.dist-info/metadata.json,sha256=5IMWsFPrWUXF547FkzP8VoOQcSGThwDKdzKEiCh6mZI,1061
aioredis-1.1.0.dist-info/top_level.txt,sha256=cjJ0NF-AaAd68k4wPn5B58ehnIXVDfCbtH1Mgzm6Gsw,9
aioredis-1.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
aioredis/__pycache__/util.cpython-36.pyc,,
aioredis/__pycache__/log.cpython-36.pyc,,
aioredis/__pycache__/pool.cpython-36.pyc,,
aioredis/__pycache__/errors.cpython-36.pyc,,
aioredis/__pycache__/connection.cpython-36.pyc,,
aioredis/__pycache__/pubsub.cpython-36.pyc,,
aioredis/__pycache__/abc.cpython-36.pyc,,
aioredis/__pycache__/parser.cpython-36.pyc,,
aioredis/__pycache__/__init__.cpython-36.pyc,,
aioredis/__pycache__/locks.cpython-36.pyc,,
aioredis/__pycache__/stream.cpython-36.pyc,,
aioredis/commands/__pycache__/hash.cpython-36.pyc,,
aioredis/commands/__pycache__/string.cpython-36.pyc,,
aioredis/commands/__pycache__/cluster.cpython-36.pyc,,
aioredis/commands/__pycache__/pubsub.cpython-36.pyc,,
aioredis/commands/__pycache__/scripting.cpython-36.pyc,,
aioredis/commands/__pycache__/server.cpython-36.pyc,,
aioredis/commands/__pycache__/generic.cpython-36.pyc,,
aioredis/commands/__pycache__/transaction.cpython-36.pyc,,
aioredis/commands/__pycache__/list.cpython-36.pyc,,
aioredis/commands/__pycache__/__init__.cpython-36.pyc,,
aioredis/commands/__pycache__/sorted_set.cpython-36.pyc,,
aioredis/commands/__pycache__/set.cpython-36.pyc,,
aioredis/commands/__pycache__/geo.cpython-36.pyc,,
aioredis/commands/__pycache__/hyperloglog.cpython-36.pyc,,
aioredis/sentinel/__pycache__/pool.cpython-36.pyc,,
aioredis/sentinel/__pycache__/__init__.cpython-36.pyc,,
aioredis/sentinel/__pycache__/commands.cpython-36.pyc,,

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1 @@
{"classifiers": ["License :: OSI Approved :: MIT License", "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3 :: Only", "Operating System :: POSIX", "Environment :: Web Environment", "Intended Audience :: Developers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Framework :: AsyncIO"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "alexey.popravka@horsedevel.com", "name": "Alexey Popravka", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/aio-libs/aioredis"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "license": "MIT", "metadata_version": "2.0", "name": "aioredis", "platform": "POSIX", "run_requires": [{"requires": ["async-timeout", "hiredis"]}], "summary": "asyncio (PEP 3156) Redis support", "version": "1.1.0"}

View File

@ -0,0 +1 @@
aioredis

View File

@ -0,0 +1,66 @@
from .connection import RedisConnection, create_connection
from .commands import (
Redis, create_redis,
create_redis_pool,
GeoPoint, GeoMember,
)
from .pool import ConnectionsPool, create_pool
from .pubsub import Channel
from .sentinel import RedisSentinel, create_sentinel
from .errors import (
ConnectionClosedError,
ConnectionForcedCloseError,
MasterNotFoundError,
MultiExecError,
PipelineError,
ProtocolError,
ReadOnlyError,
RedisError,
ReplyError,
MaxClientsError,
AuthError,
ChannelClosedError,
WatchVariableError,
PoolClosedError,
SlaveNotFoundError,
MasterReplyError,
SlaveReplyError,
)
__version__ = '1.1.0'
__all__ = [
# Factories
'create_connection',
'create_pool',
'create_redis',
'create_redis_pool',
'create_sentinel',
# Classes
'RedisConnection',
'ConnectionsPool',
'Redis',
'GeoPoint',
'GeoMember',
'Channel',
'RedisSentinel',
# Errors
'RedisError',
'ReplyError',
'MaxClientsError',
'AuthError',
'ProtocolError',
'PipelineError',
'MultiExecError',
'WatchVariableError',
'ConnectionClosedError',
'ConnectionForcedCloseError',
'PoolClosedError',
'ChannelClosedError',
'MasterNotFoundError',
'SlaveNotFoundError',
'ReadOnlyError',
'MasterReplyError',
'SlaveReplyError',
]

Some files were not shown because too many files have changed in this diff Show More