mirror of
https://github.com/neogeek23/drawshare.git
synced 2026-02-04 02:58:16 +00:00
first load, most basic functional state
This commit is contained in:
commit
218c4733db
93
.gitignore
vendored
Normal file
93
.gitignore
vendored
Normal 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
|
||||
0
ChannelsDrawShareSite/__init__.py
Normal file
0
ChannelsDrawShareSite/__init__.py
Normal file
BIN
ChannelsDrawShareSite/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
ChannelsDrawShareSite/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
ChannelsDrawShareSite/__pycache__/routing.cpython-36.pyc
Normal file
BIN
ChannelsDrawShareSite/__pycache__/routing.cpython-36.pyc
Normal file
Binary file not shown.
BIN
ChannelsDrawShareSite/__pycache__/settings.cpython-36.pyc
Normal file
BIN
ChannelsDrawShareSite/__pycache__/settings.cpython-36.pyc
Normal file
Binary file not shown.
BIN
ChannelsDrawShareSite/__pycache__/urls.cpython-36.pyc
Normal file
BIN
ChannelsDrawShareSite/__pycache__/urls.cpython-36.pyc
Normal file
Binary file not shown.
BIN
ChannelsDrawShareSite/__pycache__/wsgi.cpython-36.pyc
Normal file
BIN
ChannelsDrawShareSite/__pycache__/wsgi.cpython-36.pyc
Normal file
Binary file not shown.
13
ChannelsDrawShareSite/routing.py
Normal file
13
ChannelsDrawShareSite/routing.py
Normal 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
|
||||
)
|
||||
),
|
||||
})
|
||||
132
ChannelsDrawShareSite/settings.py
Normal file
132
ChannelsDrawShareSite/settings.py
Normal 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/'
|
||||
22
ChannelsDrawShareSite/urls.py
Normal file
22
ChannelsDrawShareSite/urls.py
Normal 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),
|
||||
]
|
||||
16
ChannelsDrawShareSite/wsgi.py
Normal file
16
ChannelsDrawShareSite/wsgi.py
Normal 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
BIN
db.sqlite3
Normal file
Binary file not shown.
0
drawshare/__init__.py
Normal file
0
drawshare/__init__.py
Normal file
BIN
drawshare/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
drawshare/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
drawshare/__pycache__/consumers.cpython-36.pyc
Normal file
BIN
drawshare/__pycache__/consumers.cpython-36.pyc
Normal file
Binary file not shown.
BIN
drawshare/__pycache__/routing.cpython-36.pyc
Normal file
BIN
drawshare/__pycache__/routing.cpython-36.pyc
Normal file
Binary file not shown.
BIN
drawshare/__pycache__/urls.cpython-36.pyc
Normal file
BIN
drawshare/__pycache__/urls.cpython-36.pyc
Normal file
Binary file not shown.
BIN
drawshare/__pycache__/views.cpython-36.pyc
Normal file
BIN
drawshare/__pycache__/views.cpython-36.pyc
Normal file
Binary file not shown.
61
drawshare/consumers.py
Normal file
61
drawshare/consumers.py
Normal 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,
|
||||
}))
|
||||
0
drawshare/migrations/__init__.py
Normal file
0
drawshare/migrations/__init__.py
Normal file
BIN
drawshare/migrations/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
drawshare/migrations/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
8
drawshare/routing.py
Normal file
8
drawshare/routing.py
Normal 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),
|
||||
]
|
||||
27
drawshare/templates/drawshare/index.html
Normal file
27
drawshare/templates/drawshare/index.html
Normal 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>
|
||||
90
drawshare/templates/drawshare/room.html
Normal file
90
drawshare/templates/drawshare/room.html
Normal 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
4
drawshare/tests.py
Normal file
@ -0,0 +1,4 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
9
drawshare/urls.py
Normal file
9
drawshare/urls.py
Normal 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
12
drawshare/views.py
Normal 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
15
manage.py
Executable 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
24
requirements.txt
Normal 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
|
||||
BIN
venv/bin/__pycache__/django-admin.cpython-36.pyc
Normal file
BIN
venv/bin/__pycache__/django-admin.cpython-36.pyc
Normal file
Binary file not shown.
76
venv/bin/activate
Normal file
76
venv/bin/activate
Normal 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
37
venv/bin/activate.csh
Normal 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
75
venv/bin/activate.fish
Normal 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
11
venv/bin/automat-visualize
Executable 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
12
venv/bin/cftp
Executable 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
12
venv/bin/ckeygen
Executable 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
12
venv/bin/conch
Executable 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
11
venv/bin/daphne
Executable 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
11
venv/bin/django-admin
Executable 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
5
venv/bin/django-admin.py
Executable 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
12
venv/bin/easy_install
Executable 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
12
venv/bin/easy_install-3.6
Executable 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
12
venv/bin/mailmail
Executable 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
12
venv/bin/pip
Executable 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
12
venv/bin/pip3
Executable 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
12
venv/bin/pip3.6
Executable 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
12
venv/bin/pyhtmlizer
Executable 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
BIN
venv/bin/python
Executable file
Binary file not shown.
BIN
venv/bin/python3
Executable file
BIN
venv/bin/python3
Executable file
Binary file not shown.
BIN
venv/bin/python3.6
Executable file
BIN
venv/bin/python3.6
Executable file
Binary file not shown.
12
venv/bin/tkconch
Executable file
12
venv/bin/tkconch
Executable 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
12
venv/bin/trial
Executable 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
12
venv/bin/twist
Executable 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
12
venv/bin/twistd
Executable 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')()
|
||||
)
|
||||
@ -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!
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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!
|
||||
|
||||
|
||||
@ -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,,
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
automat-visualize = automat._visualize:tool
|
||||
|
||||
@ -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"}
|
||||
@ -0,0 +1 @@
|
||||
automat
|
||||
@ -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
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
|
||||
4169
venv/lib/python3.6/site-packages/Django-2.1.2.dist-info/RECORD
Normal file
4169
venv/lib/python3.6/site-packages/Django-2.1.2.dist-info/RECORD
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.30.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
django-admin = django.core.management:execute_from_command_line
|
||||
|
||||
@ -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"}
|
||||
@ -0,0 +1 @@
|
||||
django
|
||||
@ -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
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,,
|
||||
@ -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
|
||||
|
||||
@ -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"}
|
||||
@ -0,0 +1 @@
|
||||
{"is_release": false, "git_version": "d572d69"}
|
||||
@ -0,0 +1 @@
|
||||
hamcrest
|
||||
@ -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
@ -0,0 +1 @@
|
||||
|
||||
@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1 @@
|
||||
twisted
|
||||
BIN
venv/lib/python3.6/site-packages/__pycache__/six.cpython-36.pyc
Normal file
BIN
venv/lib/python3.6/site-packages/__pycache__/six.cpython-36.pyc
Normal file
Binary file not shown.
@ -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.
|
||||
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
|
||||
@ -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,,
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.30.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -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"}
|
||||
@ -0,0 +1 @@
|
||||
aioredis
|
||||
66
venv/lib/python3.6/site-packages/aioredis/__init__.py
Normal file
66
venv/lib/python3.6/site-packages/aioredis/__init__.py
Normal 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',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user