Update azure package to 0.9.0

This commit is contained in:
Matthew Jones
2015-01-29 12:13:53 -05:00
parent 18e7541f3b
commit d2c46d015b
24 changed files with 15414 additions and 12009 deletions

View File

@@ -5,6 +5,7 @@ amqp==1.4.5 (amqp/*)
ansi2html==1.0.6 (ansi2html/*) ansi2html==1.0.6 (ansi2html/*)
anyjson==0.3.3 (anyjson/*) anyjson==0.3.3 (anyjson/*)
argparse==1.2.1 (argparse.py, needed for Python 2.6 support) argparse==1.2.1 (argparse.py, needed for Python 2.6 support)
azure==0.9.0 (azure/*)
Babel==1.3 (babel/*, excluded bin/pybabel) Babel==1.3 (babel/*, excluded bin/pybabel)
billiard==3.3.0.16 (billiard/*, funtests/*, excluded _billiard.so) billiard==3.3.0.16 (billiard/*, funtests/*, excluded _billiard.so)
boto==2.34.0 (boto/*, excluded bin/asadmin, bin/bundle_image, bin/cfadmin, boto==2.34.0 (boto/*, excluded bin/asadmin, bin/bundle_image, bin/cfadmin,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{25b2c65a-0553-4452-8907-8b5b17544e68}</ProjectGuid>
<ProjectHome>
</ProjectHome>
<StartupFile>storage\blobservice.py</StartupFile>
<SearchPath>..</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<Name>azure</Name>
<RootNamespace>azure</RootNamespace>
<IsWindowsApplication>False</IsWindowsApplication>
<LaunchProvider>Standard Python launcher</LaunchProvider>
<CommandLineArguments />
<InterpreterPath />
<InterpreterArguments />
<InterpreterId>{2af0f10d-7135-4994-9156-5d01c9c11b7e}</InterpreterId>
<InterpreterVersion>2.7</InterpreterVersion>
<SccProjectName>SAK</SccProjectName>
<SccProvider>SAK</SccProvider>
<SccAuxPath>SAK</SccAuxPath>
<SccLocalPath>SAK</SccLocalPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<ItemGroup>
<Compile Include="http\batchclient.py" />
<Compile Include="http\httpclient.py" />
<Compile Include="http\requestsclient.py" />
<Compile Include="http\winhttp.py" />
<Compile Include="http\__init__.py" />
<Compile Include="servicemanagement\schedulermanagementservice.py" />
<Compile Include="servicemanagement\servicebusmanagementservice.py" />
<Compile Include="servicemanagement\servicemanagementclient.py" />
<Compile Include="servicemanagement\servicemanagementservice.py" />
<Compile Include="servicemanagement\sqldatabasemanagementservice.py" />
<Compile Include="servicemanagement\websitemanagementservice.py" />
<Compile Include="servicemanagement\__init__.py" />
<Compile Include="servicebus\servicebusservice.py" />
<Compile Include="storage\blobservice.py" />
<Compile Include="storage\queueservice.py" />
<Compile Include="storage\cloudstorageaccount.py" />
<Compile Include="storage\tableservice.py" />
<Compile Include="storage\sharedaccesssignature.py" />
<Compile Include="__init__.py" />
<Compile Include="servicebus\__init__.py" />
<Compile Include="storage\storageclient.py" />
<Compile Include="storage\__init__.py" />
</ItemGroup>
<ItemGroup>
<Folder Include="http" />
<Folder Include="servicemanagement" />
<Folder Include="servicebus" />
<Folder Include="storage" />
</ItemGroup>
<ItemGroup>
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\2.6" />
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\2.7" />
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\3.3" />
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\3.4" />
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\2.7" />
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\3.3" />
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\3.4" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<PtvsTargetsFile>$(VSToolsPath)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
</PropertyGroup>
<Import Condition="Exists($(PtvsTargetsFile))" Project="$(PtvsTargetsFile)" />
<Import Condition="!Exists($(PtvsTargetsFile))" Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
</Project>

View File

@@ -1,73 +1,73 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
HTTP_RESPONSE_NO_CONTENT = 204 HTTP_RESPONSE_NO_CONTENT = 204
class HTTPError(Exception): class HTTPError(Exception):
''' HTTP Exception when response status code >= 300 ''' ''' HTTP Exception when response status code >= 300 '''
def __init__(self, status, message, respheader, respbody): def __init__(self, status, message, respheader, respbody):
'''Creates a new HTTPError with the specified status, message, '''Creates a new HTTPError with the specified status, message,
response headers and body''' response headers and body'''
self.status = status self.status = status
self.respheader = respheader self.respheader = respheader
self.respbody = respbody self.respbody = respbody
Exception.__init__(self, message) Exception.__init__(self, message)
class HTTPResponse(object): class HTTPResponse(object):
"""Represents a response from an HTTP request. An HTTPResponse has the """Represents a response from an HTTP request. An HTTPResponse has the
following attributes: following attributes:
status: the status code of the response status: the status code of the response
message: the message message: the message
headers: the returned headers, as a list of (name, value) pairs headers: the returned headers, as a list of (name, value) pairs
body: the body of the response body: the body of the response
""" """
def __init__(self, status, message, headers, body): def __init__(self, status, message, headers, body):
self.status = status self.status = status
self.message = message self.message = message
self.headers = headers self.headers = headers
self.body = body self.body = body
class HTTPRequest(object): class HTTPRequest(object):
'''Represents an HTTP Request. An HTTP Request consists of the following '''Represents an HTTP Request. An HTTP Request consists of the following
attributes: attributes:
host: the host name to connect to host: the host name to connect to
method: the method to use to connect (string such as GET, POST, PUT, etc.) method: the method to use to connect (string such as GET, POST, PUT, etc.)
path: the uri fragment path: the uri fragment
query: query parameters specified as a list of (name, value) pairs query: query parameters specified as a list of (name, value) pairs
headers: header values specified as (name, value) pairs headers: header values specified as (name, value) pairs
body: the body of the request. body: the body of the request.
protocol_override: protocol_override:
specify to use this protocol instead of the global one stored in specify to use this protocol instead of the global one stored in
_HTTPClient. _HTTPClient.
''' '''
def __init__(self): def __init__(self):
self.host = '' self.host = ''
self.method = '' self.method = ''
self.path = '' self.path = ''
self.query = [] # list of (name, value) self.query = [] # list of (name, value)
self.headers = [] # list of (header name, header value) self.headers = [] # list of (header name, header value)
self.body = '' self.body = ''
self.protocol_override = None self.protocol_override = None

View File

@@ -1,339 +1,339 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
import sys import sys
import uuid import uuid
from azure import ( from azure import (
_update_request_uri_query, _update_request_uri_query,
WindowsAzureError, WindowsAzureError,
WindowsAzureBatchOperationError, WindowsAzureBatchOperationError,
_get_children_from_path, _get_children_from_path,
url_unquote, url_unquote,
_ERROR_CANNOT_FIND_PARTITION_KEY, _ERROR_CANNOT_FIND_PARTITION_KEY,
_ERROR_CANNOT_FIND_ROW_KEY, _ERROR_CANNOT_FIND_ROW_KEY,
_ERROR_INCORRECT_TABLE_IN_BATCH, _ERROR_INCORRECT_TABLE_IN_BATCH,
_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH, _ERROR_INCORRECT_PARTITION_KEY_IN_BATCH,
_ERROR_DUPLICATE_ROW_KEY_IN_BATCH, _ERROR_DUPLICATE_ROW_KEY_IN_BATCH,
_ERROR_BATCH_COMMIT_FAIL, _ERROR_BATCH_COMMIT_FAIL,
) )
from azure.http import HTTPError, HTTPRequest, HTTPResponse from azure.http import HTTPError, HTTPRequest, HTTPResponse
from azure.http.httpclient import _HTTPClient from azure.http.httpclient import _HTTPClient
from azure.storage import ( from azure.storage import (
_update_storage_table_header, _update_storage_table_header,
METADATA_NS, METADATA_NS,
_sign_storage_table_request, _sign_storage_table_request,
) )
from xml.dom import minidom from xml.dom import minidom
_DATASERVICES_NS = 'http://schemas.microsoft.com/ado/2007/08/dataservices' _DATASERVICES_NS = 'http://schemas.microsoft.com/ado/2007/08/dataservices'
if sys.version_info < (3,): if sys.version_info < (3,):
def _new_boundary(): def _new_boundary():
return str(uuid.uuid1()) return str(uuid.uuid1())
else: else:
def _new_boundary(): def _new_boundary():
return str(uuid.uuid1()).encode('utf-8') return str(uuid.uuid1()).encode('utf-8')
class _BatchClient(_HTTPClient): class _BatchClient(_HTTPClient):
''' '''
This is the class that is used for batch operation for storage table This is the class that is used for batch operation for storage table
service. It only supports one changeset. service. It only supports one changeset.
''' '''
def __init__(self, service_instance, account_key, account_name, def __init__(self, service_instance, account_key, account_name,
protocol='http'): protocol='http'):
_HTTPClient.__init__(self, service_instance, account_name=account_name, _HTTPClient.__init__(self, service_instance, account_name=account_name,
account_key=account_key, protocol=protocol) account_key=account_key, protocol=protocol)
self.is_batch = False self.is_batch = False
self.batch_requests = [] self.batch_requests = []
self.batch_table = '' self.batch_table = ''
self.batch_partition_key = '' self.batch_partition_key = ''
self.batch_row_keys = [] self.batch_row_keys = []
def get_request_table(self, request): def get_request_table(self, request):
''' '''
Extracts table name from request.uri. The request.uri has either Extracts table name from request.uri. The request.uri has either
"/mytable(...)" or "/mytable" format. "/mytable(...)" or "/mytable" format.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
if '(' in request.path: if '(' in request.path:
pos = request.path.find('(') pos = request.path.find('(')
return request.path[1:pos] return request.path[1:pos]
else: else:
return request.path[1:] return request.path[1:]
def get_request_partition_key(self, request): def get_request_partition_key(self, request):
''' '''
Extracts PartitionKey from request.body if it is a POST request or from Extracts PartitionKey from request.body if it is a POST request or from
request.path if it is not a POST request. Only insert operation request request.path if it is not a POST request. Only insert operation request
is a POST request and the PartitionKey is in the request body. is a POST request and the PartitionKey is in the request body.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
if request.method == 'POST': if request.method == 'POST':
doc = minidom.parseString(request.body) doc = minidom.parseString(request.body)
part_key = _get_children_from_path( part_key = _get_children_from_path(
doc, 'entry', 'content', (METADATA_NS, 'properties'), doc, 'entry', 'content', (METADATA_NS, 'properties'),
(_DATASERVICES_NS, 'PartitionKey')) (_DATASERVICES_NS, 'PartitionKey'))
if not part_key: if not part_key:
raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY) raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY)
return part_key[0].firstChild.nodeValue return part_key[0].firstChild.nodeValue
else: else:
uri = url_unquote(request.path) uri = url_unquote(request.path)
pos1 = uri.find('PartitionKey=\'') pos1 = uri.find('PartitionKey=\'')
pos2 = uri.find('\',', pos1) pos2 = uri.find('\',', pos1)
if pos1 == -1 or pos2 == -1: if pos1 == -1 or pos2 == -1:
raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY) raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY)
return uri[pos1 + len('PartitionKey=\''):pos2] return uri[pos1 + len('PartitionKey=\''):pos2]
def get_request_row_key(self, request): def get_request_row_key(self, request):
''' '''
Extracts RowKey from request.body if it is a POST request or from Extracts RowKey from request.body if it is a POST request or from
request.path if it is not a POST request. Only insert operation request request.path if it is not a POST request. Only insert operation request
is a POST request and the Rowkey is in the request body. is a POST request and the Rowkey is in the request body.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
if request.method == 'POST': if request.method == 'POST':
doc = minidom.parseString(request.body) doc = minidom.parseString(request.body)
row_key = _get_children_from_path( row_key = _get_children_from_path(
doc, 'entry', 'content', (METADATA_NS, 'properties'), doc, 'entry', 'content', (METADATA_NS, 'properties'),
(_DATASERVICES_NS, 'RowKey')) (_DATASERVICES_NS, 'RowKey'))
if not row_key: if not row_key:
raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY) raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY)
return row_key[0].firstChild.nodeValue return row_key[0].firstChild.nodeValue
else: else:
uri = url_unquote(request.path) uri = url_unquote(request.path)
pos1 = uri.find('RowKey=\'') pos1 = uri.find('RowKey=\'')
pos2 = uri.find('\')', pos1) pos2 = uri.find('\')', pos1)
if pos1 == -1 or pos2 == -1: if pos1 == -1 or pos2 == -1:
raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY) raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY)
row_key = uri[pos1 + len('RowKey=\''):pos2] row_key = uri[pos1 + len('RowKey=\''):pos2]
return row_key return row_key
def validate_request_table(self, request): def validate_request_table(self, request):
''' '''
Validates that all requests have the same table name. Set the table Validates that all requests have the same table name. Set the table
name if it is the first request for the batch operation. name if it is the first request for the batch operation.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
if self.batch_table: if self.batch_table:
if self.get_request_table(request) != self.batch_table: if self.get_request_table(request) != self.batch_table:
raise WindowsAzureError(_ERROR_INCORRECT_TABLE_IN_BATCH) raise WindowsAzureError(_ERROR_INCORRECT_TABLE_IN_BATCH)
else: else:
self.batch_table = self.get_request_table(request) self.batch_table = self.get_request_table(request)
def validate_request_partition_key(self, request): def validate_request_partition_key(self, request):
''' '''
Validates that all requests have the same PartitiionKey. Set the Validates that all requests have the same PartitiionKey. Set the
PartitionKey if it is the first request for the batch operation. PartitionKey if it is the first request for the batch operation.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
if self.batch_partition_key: if self.batch_partition_key:
if self.get_request_partition_key(request) != \ if self.get_request_partition_key(request) != \
self.batch_partition_key: self.batch_partition_key:
raise WindowsAzureError(_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH) raise WindowsAzureError(_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH)
else: else:
self.batch_partition_key = self.get_request_partition_key(request) self.batch_partition_key = self.get_request_partition_key(request)
def validate_request_row_key(self, request): def validate_request_row_key(self, request):
''' '''
Validates that all requests have the different RowKey and adds RowKey Validates that all requests have the different RowKey and adds RowKey
to existing RowKey list. to existing RowKey list.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
if self.batch_row_keys: if self.batch_row_keys:
if self.get_request_row_key(request) in self.batch_row_keys: if self.get_request_row_key(request) in self.batch_row_keys:
raise WindowsAzureError(_ERROR_DUPLICATE_ROW_KEY_IN_BATCH) raise WindowsAzureError(_ERROR_DUPLICATE_ROW_KEY_IN_BATCH)
else: else:
self.batch_row_keys.append(self.get_request_row_key(request)) self.batch_row_keys.append(self.get_request_row_key(request))
def begin_batch(self): def begin_batch(self):
''' '''
Starts the batch operation. Intializes the batch variables Starts the batch operation. Intializes the batch variables
is_batch: batch operation flag. is_batch: batch operation flag.
batch_table: the table name of the batch operation batch_table: the table name of the batch operation
batch_partition_key: the PartitionKey of the batch requests. batch_partition_key: the PartitionKey of the batch requests.
batch_row_keys: the RowKey list of adding requests. batch_row_keys: the RowKey list of adding requests.
batch_requests: the list of the requests. batch_requests: the list of the requests.
''' '''
self.is_batch = True self.is_batch = True
self.batch_table = '' self.batch_table = ''
self.batch_partition_key = '' self.batch_partition_key = ''
self.batch_row_keys = [] self.batch_row_keys = []
self.batch_requests = [] self.batch_requests = []
def insert_request_to_batch(self, request): def insert_request_to_batch(self, request):
''' '''
Adds request to batch operation. Adds request to batch operation.
request: the request to insert, update or delete entity request: the request to insert, update or delete entity
''' '''
self.validate_request_table(request) self.validate_request_table(request)
self.validate_request_partition_key(request) self.validate_request_partition_key(request)
self.validate_request_row_key(request) self.validate_request_row_key(request)
self.batch_requests.append(request) self.batch_requests.append(request)
def commit_batch(self): def commit_batch(self):
''' Resets batch flag and commits the batch requests. ''' ''' Resets batch flag and commits the batch requests. '''
if self.is_batch: if self.is_batch:
self.is_batch = False self.is_batch = False
self.commit_batch_requests() self.commit_batch_requests()
def commit_batch_requests(self): def commit_batch_requests(self):
''' Commits the batch requests. ''' ''' Commits the batch requests. '''
batch_boundary = b'batch_' + _new_boundary() batch_boundary = b'batch_' + _new_boundary()
changeset_boundary = b'changeset_' + _new_boundary() changeset_boundary = b'changeset_' + _new_boundary()
# Commits batch only the requests list is not empty. # Commits batch only the requests list is not empty.
if self.batch_requests: if self.batch_requests:
request = HTTPRequest() request = HTTPRequest()
request.method = 'POST' request.method = 'POST'
request.host = self.batch_requests[0].host request.host = self.batch_requests[0].host
request.path = '/$batch' request.path = '/$batch'
request.headers = [ request.headers = [
('Content-Type', 'multipart/mixed; boundary=' + \ ('Content-Type', 'multipart/mixed; boundary=' + \
batch_boundary.decode('utf-8')), batch_boundary.decode('utf-8')),
('Accept', 'application/atom+xml,application/xml'), ('Accept', 'application/atom+xml,application/xml'),
('Accept-Charset', 'UTF-8')] ('Accept-Charset', 'UTF-8')]
request.body = b'--' + batch_boundary + b'\n' request.body = b'--' + batch_boundary + b'\n'
request.body += b'Content-Type: multipart/mixed; boundary=' request.body += b'Content-Type: multipart/mixed; boundary='
request.body += changeset_boundary + b'\n\n' request.body += changeset_boundary + b'\n\n'
content_id = 1 content_id = 1
# Adds each request body to the POST data. # Adds each request body to the POST data.
for batch_request in self.batch_requests: for batch_request in self.batch_requests:
request.body += b'--' + changeset_boundary + b'\n' request.body += b'--' + changeset_boundary + b'\n'
request.body += b'Content-Type: application/http\n' request.body += b'Content-Type: application/http\n'
request.body += b'Content-Transfer-Encoding: binary\n\n' request.body += b'Content-Transfer-Encoding: binary\n\n'
request.body += batch_request.method.encode('utf-8') request.body += batch_request.method.encode('utf-8')
request.body += b' http://' request.body += b' http://'
request.body += batch_request.host.encode('utf-8') request.body += batch_request.host.encode('utf-8')
request.body += batch_request.path.encode('utf-8') request.body += batch_request.path.encode('utf-8')
request.body += b' HTTP/1.1\n' request.body += b' HTTP/1.1\n'
request.body += b'Content-ID: ' request.body += b'Content-ID: '
request.body += str(content_id).encode('utf-8') + b'\n' request.body += str(content_id).encode('utf-8') + b'\n'
content_id += 1 content_id += 1
# Add different headers for different type requests. # Add different headers for different type requests.
if not batch_request.method == 'DELETE': if not batch_request.method == 'DELETE':
request.body += \ request.body += \
b'Content-Type: application/atom+xml;type=entry\n' b'Content-Type: application/atom+xml;type=entry\n'
for name, value in batch_request.headers: for name, value in batch_request.headers:
if name == 'If-Match': if name == 'If-Match':
request.body += name.encode('utf-8') + b': ' request.body += name.encode('utf-8') + b': '
request.body += value.encode('utf-8') + b'\n' request.body += value.encode('utf-8') + b'\n'
break break
request.body += b'Content-Length: ' request.body += b'Content-Length: '
request.body += str(len(batch_request.body)).encode('utf-8') request.body += str(len(batch_request.body)).encode('utf-8')
request.body += b'\n\n' request.body += b'\n\n'
request.body += batch_request.body + b'\n' request.body += batch_request.body + b'\n'
else: else:
for name, value in batch_request.headers: for name, value in batch_request.headers:
# If-Match should be already included in # If-Match should be already included in
# batch_request.headers, but in case it is missing, # batch_request.headers, but in case it is missing,
# just add it. # just add it.
if name == 'If-Match': if name == 'If-Match':
request.body += name.encode('utf-8') + b': ' request.body += name.encode('utf-8') + b': '
request.body += value.encode('utf-8') + b'\n\n' request.body += value.encode('utf-8') + b'\n\n'
break break
else: else:
request.body += b'If-Match: *\n\n' request.body += b'If-Match: *\n\n'
request.body += b'--' + changeset_boundary + b'--' + b'\n' request.body += b'--' + changeset_boundary + b'--' + b'\n'
request.body += b'--' + batch_boundary + b'--' request.body += b'--' + batch_boundary + b'--'
request.path, request.query = _update_request_uri_query(request) request.path, request.query = _update_request_uri_query(request)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
auth = _sign_storage_table_request(request, auth = _sign_storage_table_request(request,
self.account_name, self.account_name,
self.account_key) self.account_key)
request.headers.append(('Authorization', auth)) request.headers.append(('Authorization', auth))
# Submit the whole request as batch request. # Submit the whole request as batch request.
response = self.perform_request(request) response = self.perform_request(request)
if response.status >= 300: if response.status >= 300:
raise HTTPError(response.status, raise HTTPError(response.status,
_ERROR_BATCH_COMMIT_FAIL, _ERROR_BATCH_COMMIT_FAIL,
self.respheader, self.respheader,
response.body) response.body)
# http://www.odata.org/documentation/odata-version-2-0/batch-processing/ # http://www.odata.org/documentation/odata-version-2-0/batch-processing/
# The body of a ChangeSet response is either a response for all the # The body of a ChangeSet response is either a response for all the
# successfully processed change request within the ChangeSet, # successfully processed change request within the ChangeSet,
# formatted exactly as it would have appeared outside of a batch, # formatted exactly as it would have appeared outside of a batch,
# or a single response indicating a failure of the entire ChangeSet. # or a single response indicating a failure of the entire ChangeSet.
responses = self._parse_batch_response(response.body) responses = self._parse_batch_response(response.body)
if responses and responses[0].status >= 300: if responses and responses[0].status >= 300:
self._report_batch_error(responses[0]) self._report_batch_error(responses[0])
def cancel_batch(self): def cancel_batch(self):
''' Resets the batch flag. ''' ''' Resets the batch flag. '''
self.is_batch = False self.is_batch = False
def _parse_batch_response(self, body): def _parse_batch_response(self, body):
parts = body.split(b'--changesetresponse_') parts = body.split(b'--changesetresponse_')
responses = [] responses = []
for part in parts: for part in parts:
httpLocation = part.find(b'HTTP/') httpLocation = part.find(b'HTTP/')
if httpLocation > 0: if httpLocation > 0:
response = self._parse_batch_response_part(part[httpLocation:]) response = self._parse_batch_response_part(part[httpLocation:])
responses.append(response) responses.append(response)
return responses return responses
def _parse_batch_response_part(self, part): def _parse_batch_response_part(self, part):
lines = part.splitlines(); lines = part.splitlines();
# First line is the HTTP status/reason # First line is the HTTP status/reason
status, _, reason = lines[0].partition(b' ')[2].partition(b' ') status, _, reason = lines[0].partition(b' ')[2].partition(b' ')
# Followed by headers and body # Followed by headers and body
headers = [] headers = []
body = b'' body = b''
isBody = False isBody = False
for line in lines[1:]: for line in lines[1:]:
if line == b'' and not isBody: if line == b'' and not isBody:
isBody = True isBody = True
elif isBody: elif isBody:
body += line body += line
else: else:
headerName, _, headerVal = line.partition(b':') headerName, _, headerVal = line.partition(b':')
headers.append((headerName.lower(), headerVal)) headers.append((headerName.lower(), headerVal))
return HTTPResponse(int(status), reason.strip(), headers, body) return HTTPResponse(int(status), reason.strip(), headers, body)
def _report_batch_error(self, response): def _report_batch_error(self, response):
xml = response.body.decode('utf-8') xml = response.body.decode('utf-8')
doc = minidom.parseString(xml) doc = minidom.parseString(xml)
n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'code') n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'code')
code = n[0].firstChild.nodeValue if n and n[0].firstChild else '' code = n[0].firstChild.nodeValue if n and n[0].firstChild else ''
n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'message') n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'message')
message = n[0].firstChild.nodeValue if n and n[0].firstChild else xml message = n[0].firstChild.nodeValue if n and n[0].firstChild else xml
raise WindowsAzureBatchOperationError(message, code) raise WindowsAzureBatchOperationError(message, code)

View File

@@ -1,223 +1,251 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
import base64 import base64
import os import os
import sys import sys
if sys.version_info < (3,): if sys.version_info < (3,):
from httplib import ( from httplib import (
HTTPSConnection, HTTPSConnection,
HTTPConnection, HTTPConnection,
HTTP_PORT, HTTP_PORT,
HTTPS_PORT, HTTPS_PORT,
) )
from urlparse import urlparse from urlparse import urlparse
else: else:
from http.client import ( from http.client import (
HTTPSConnection, HTTPSConnection,
HTTPConnection, HTTPConnection,
HTTP_PORT, HTTP_PORT,
HTTPS_PORT, HTTPS_PORT,
) )
from urllib.parse import urlparse from urllib.parse import urlparse
from azure.http import HTTPError, HTTPResponse from azure.http import HTTPError, HTTPResponse
from azure import _USER_AGENT_STRING, _update_request_uri_query from azure import _USER_AGENT_STRING, _update_request_uri_query
DEBUG_REQUESTS = False
class _HTTPClient(object): DEBUG_RESPONSES = False
''' class _HTTPClient(object):
Takes the request and sends it to cloud service and returns the response.
''' '''
Takes the request and sends it to cloud service and returns the response.
def __init__(self, service_instance, cert_file=None, account_name=None, '''
account_key=None, service_namespace=None, issuer=None,
protocol='https'): def __init__(self, service_instance, cert_file=None, account_name=None,
''' account_key=None, protocol='https', request_session=None):
service_instance: service client instance. '''
cert_file: service_instance: service client instance.
certificate file name/location. This is only used in hosted cert_file:
service management. certificate file name/location. This is only used in hosted
account_name: the storage account. service management.
account_key: account_name: the storage account.
the storage account access key for storage services or servicebus account_key:
access key for service bus service. the storage account access key.
service_namespace: the service namespace for service bus. request_session:
issuer: the issuer for service bus service. session object created with requests library (or compatible).
''' '''
self.service_instance = service_instance self.service_instance = service_instance
self.status = None self.status = None
self.respheader = None self.respheader = None
self.message = None self.message = None
self.cert_file = cert_file self.cert_file = cert_file
self.account_name = account_name self.account_name = account_name
self.account_key = account_key self.account_key = account_key
self.service_namespace = service_namespace self.protocol = protocol
self.issuer = issuer self.proxy_host = None
self.protocol = protocol self.proxy_port = None
self.proxy_host = None self.proxy_user = None
self.proxy_port = None self.proxy_password = None
self.proxy_user = None self.request_session = request_session
self.proxy_password = None if request_session:
self.use_httplib = self.should_use_httplib() self.use_httplib = True
else:
def should_use_httplib(self): self.use_httplib = self.should_use_httplib()
if sys.platform.lower().startswith('win') and self.cert_file:
# On Windows, auto-detect between Windows Store Certificate def should_use_httplib(self):
# (winhttp) and OpenSSL .pem certificate file (httplib). if sys.platform.lower().startswith('win') and self.cert_file:
# # On Windows, auto-detect between Windows Store Certificate
# We used to only support certificates installed in the Windows # (winhttp) and OpenSSL .pem certificate file (httplib).
# Certificate Store. #
# cert_file example: CURRENT_USER\my\CertificateName # We used to only support certificates installed in the Windows
# # Certificate Store.
# We now support using an OpenSSL .pem certificate file, # cert_file example: CURRENT_USER\my\CertificateName
# for a consistent experience across all platforms. #
# cert_file example: account\certificate.pem # We now support using an OpenSSL .pem certificate file,
# # for a consistent experience across all platforms.
# When using OpenSSL .pem certificate file on Windows, make sure # cert_file example: account\certificate.pem
# you are on CPython 2.7.4 or later. #
# When using OpenSSL .pem certificate file on Windows, make sure
# If it's not an existing file on disk, then treat it as a path in # you are on CPython 2.7.4 or later.
# the Windows Certificate Store, which means we can't use httplib.
if not os.path.isfile(self.cert_file): # If it's not an existing file on disk, then treat it as a path in
return False # the Windows Certificate Store, which means we can't use httplib.
if not os.path.isfile(self.cert_file):
return True return False
def set_proxy(self, host, port, user, password): return True
'''
Sets the proxy server host and port for the HTTP CONNECT Tunnelling. def set_proxy(self, host, port, user, password):
'''
host: Address of the proxy. Ex: '192.168.0.100' Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
port: Port of the proxy. Ex: 6000
user: User for proxy authorization. host: Address of the proxy. Ex: '192.168.0.100'
password: Password for proxy authorization. port: Port of the proxy. Ex: 6000
''' user: User for proxy authorization.
self.proxy_host = host password: Password for proxy authorization.
self.proxy_port = port '''
self.proxy_user = user self.proxy_host = host
self.proxy_password = password self.proxy_port = port
self.proxy_user = user
def get_connection(self, request): self.proxy_password = password
''' Create connection for the request. '''
protocol = request.protocol_override \ def get_uri(self, request):
if request.protocol_override else self.protocol ''' Return the target uri for the request.'''
target_host = request.host protocol = request.protocol_override \
target_port = HTTP_PORT if protocol == 'http' else HTTPS_PORT if request.protocol_override else self.protocol
port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
if not self.use_httplib: return protocol + '://' + request.host + ':' + str(port) + request.path
import azure.http.winhttp
connection = azure.http.winhttp._HTTPConnection( def get_connection(self, request):
target_host, cert_file=self.cert_file, protocol=protocol) ''' Create connection for the request. '''
proxy_host = self.proxy_host protocol = request.protocol_override \
proxy_port = self.proxy_port if request.protocol_override else self.protocol
else: target_host = request.host
if ':' in target_host: target_port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
target_host, _, target_port = target_host.rpartition(':')
if self.proxy_host: if self.request_session:
proxy_host = target_host import azure.http.requestsclient
proxy_port = target_port connection = azure.http.requestsclient._RequestsConnection(
host = self.proxy_host target_host, protocol, self.request_session)
port = self.proxy_port #TODO: proxy stuff
else: elif not self.use_httplib:
host = target_host import azure.http.winhttp
port = target_port connection = azure.http.winhttp._HTTPConnection(
target_host, cert_file=self.cert_file, protocol=protocol)
if protocol == 'http': proxy_host = self.proxy_host
connection = HTTPConnection(host, int(port)) proxy_port = self.proxy_port
else: else:
connection = HTTPSConnection( if ':' in target_host:
host, int(port), cert_file=self.cert_file) target_host, _, target_port = target_host.rpartition(':')
if self.proxy_host:
if self.proxy_host: proxy_host = target_host
headers = None proxy_port = target_port
if self.proxy_user and self.proxy_password: host = self.proxy_host
auth = base64.encodestring( port = self.proxy_port
"{0}:{1}".format(self.proxy_user, self.proxy_password)) else:
headers = {'Proxy-Authorization': 'Basic {0}'.format(auth)} host = target_host
connection.set_tunnel(proxy_host, int(proxy_port), headers) port = target_port
return connection if protocol == 'http':
connection = HTTPConnection(host, int(port))
def send_request_headers(self, connection, request_headers): else:
if self.use_httplib: connection = HTTPSConnection(
if self.proxy_host: host, int(port), cert_file=self.cert_file)
for i in connection._buffer:
if i.startswith("Host: "): if self.proxy_host:
connection._buffer.remove(i) headers = None
connection.putheader( if self.proxy_user and self.proxy_password:
'Host', "{0}:{1}".format(connection._tunnel_host, auth = base64.encodestring(
connection._tunnel_port)) "{0}:{1}".format(self.proxy_user, self.proxy_password))
headers = {'Proxy-Authorization': 'Basic {0}'.format(auth)}
for name, value in request_headers: connection.set_tunnel(proxy_host, int(proxy_port), headers)
if value:
connection.putheader(name, value) return connection
connection.putheader('User-Agent', _USER_AGENT_STRING) def send_request_headers(self, connection, request_headers):
connection.endheaders() if self.use_httplib:
if self.proxy_host:
def send_request_body(self, connection, request_body): for i in connection._buffer:
if request_body: if i.startswith("Host: "):
assert isinstance(request_body, bytes) connection._buffer.remove(i)
connection.send(request_body) connection.putheader(
elif (not isinstance(connection, HTTPSConnection) and 'Host', "{0}:{1}".format(connection._tunnel_host,
not isinstance(connection, HTTPConnection)): connection._tunnel_port))
connection.send(None)
for name, value in request_headers:
def perform_request(self, request): if value:
''' Sends request to cloud service server and return the response. ''' connection.putheader(name, value)
connection = self.get_connection(request)
try: connection.putheader('User-Agent', _USER_AGENT_STRING)
connection.putrequest(request.method, request.path) connection.endheaders()
if not self.use_httplib: def send_request_body(self, connection, request_body):
if self.proxy_host and self.proxy_user: if request_body:
connection.set_proxy_credentials( assert isinstance(request_body, bytes)
self.proxy_user, self.proxy_password) connection.send(request_body)
elif (not isinstance(connection, HTTPSConnection) and
self.send_request_headers(connection, request.headers) not isinstance(connection, HTTPConnection)):
self.send_request_body(connection, request.body) connection.send(None)
resp = connection.getresponse() def perform_request(self, request):
self.status = int(resp.status) ''' Sends request to cloud service server and return the response. '''
self.message = resp.reason connection = self.get_connection(request)
self.respheader = headers = resp.getheaders() try:
connection.putrequest(request.method, request.path)
# for consistency across platforms, make header names lowercase
for i, value in enumerate(headers): if not self.use_httplib:
headers[i] = (value[0].lower(), value[1]) if self.proxy_host and self.proxy_user:
connection.set_proxy_credentials(
respbody = None self.proxy_user, self.proxy_password)
if resp.length is None:
respbody = resp.read() self.send_request_headers(connection, request.headers)
elif resp.length > 0: self.send_request_body(connection, request.body)
respbody = resp.read(resp.length)
if DEBUG_REQUESTS and request.body:
response = HTTPResponse( print('request:')
int(resp.status), resp.reason, headers, respbody) try:
if self.status == 307: print(request.body)
new_url = urlparse(dict(headers)['location']) except:
request.host = new_url.hostname pass
request.path = new_url.path
request.path, request.query = _update_request_uri_query(request) resp = connection.getresponse()
return self.perform_request(request) self.status = int(resp.status)
if self.status >= 300: self.message = resp.reason
raise HTTPError(self.status, self.message, self.respheader = headers = resp.getheaders()
self.respheader, respbody)
# for consistency across platforms, make header names lowercase
return response for i, value in enumerate(headers):
finally: headers[i] = (value[0].lower(), value[1])
connection.close()
respbody = None
if resp.length is None:
respbody = resp.read()
elif resp.length > 0:
respbody = resp.read(resp.length)
if DEBUG_RESPONSES and respbody:
print('response:')
try:
print(respbody)
except:
pass
response = HTTPResponse(
int(resp.status), resp.reason, headers, respbody)
if self.status == 307:
new_url = urlparse(dict(headers)['location'])
request.host = new_url.hostname
request.path = new_url.path
request.path, request.query = _update_request_uri_query(request)
return self.perform_request(request)
if self.status >= 300:
raise HTTPError(self.status, self.message,
self.respheader, respbody)
return response
finally:
connection.close()

View File

@@ -0,0 +1,74 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#--------------------------------------------------------------------------
class _Response(object):
''' Response class corresponding to the response returned from httplib
HTTPConnection. '''
def __init__(self, response):
self.status = response.status_code
self.reason = response.reason
self.respbody = response.content
self.length = len(response.content)
self.headers = []
for key, name in response.headers.items():
self.headers.append((key.lower(), name))
def getheaders(self):
'''Returns response headers.'''
return self.headers
def read(self, _length):
'''Returns response body. '''
return self.respbody[:_length]
class _RequestsConnection(object):
def __init__(self, host, protocol, session):
self.host = host
self.protocol = protocol
self.session = session
self.headers = {}
self.method = None
self.body = None
self.response = None
self.uri = None
def close(self):
pass
def set_tunnel(self, host, port=None, headers=None):
pass
def set_proxy_credentials(self, user, password):
pass
def putrequest(self, method, uri):
self.method = method
self.uri = self.protocol + '://' + self.host + uri
def putheader(self, name, value):
self.headers[name] = value
def endheaders(self):
pass
def send(self, request_body):
self.response = self.session.request(self.method, self.uri, data=request_body, headers=self.headers)
def getresponse(self):
return _Response(self.response)

View File

@@ -1,471 +1,471 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
from ctypes import ( from ctypes import (
c_void_p, c_void_p,
c_long, c_long,
c_ulong, c_ulong,
c_longlong, c_longlong,
c_ulonglong, c_ulonglong,
c_short, c_short,
c_ushort, c_ushort,
c_wchar_p, c_wchar_p,
c_byte, c_byte,
byref, byref,
Structure, Structure,
Union, Union,
POINTER, POINTER,
WINFUNCTYPE, WINFUNCTYPE,
HRESULT, HRESULT,
oledll, oledll,
WinDLL, WinDLL,
) )
import ctypes import ctypes
import sys import sys
if sys.version_info >= (3,): if sys.version_info >= (3,):
def unicode(text): def unicode(text):
return text return text
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Constants that are used in COM operations # Constants that are used in COM operations
VT_EMPTY = 0 VT_EMPTY = 0
VT_NULL = 1 VT_NULL = 1
VT_I2 = 2 VT_I2 = 2
VT_I4 = 3 VT_I4 = 3
VT_BSTR = 8 VT_BSTR = 8
VT_BOOL = 11 VT_BOOL = 11
VT_I1 = 16 VT_I1 = 16
VT_UI1 = 17 VT_UI1 = 17
VT_UI2 = 18 VT_UI2 = 18
VT_UI4 = 19 VT_UI4 = 19
VT_I8 = 20 VT_I8 = 20
VT_UI8 = 21 VT_UI8 = 21
VT_ARRAY = 8192 VT_ARRAY = 8192
HTTPREQUEST_PROXYSETTING_PROXY = 2 HTTPREQUEST_PROXYSETTING_PROXY = 2
HTTPREQUEST_SETCREDENTIALS_FOR_PROXY = 1 HTTPREQUEST_SETCREDENTIALS_FOR_PROXY = 1
HTTPREQUEST_PROXY_SETTING = c_long HTTPREQUEST_PROXY_SETTING = c_long
HTTPREQUEST_SETCREDENTIALS_FLAGS = c_long HTTPREQUEST_SETCREDENTIALS_FLAGS = c_long
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Com related APIs that are used. # Com related APIs that are used.
_ole32 = oledll.ole32 _ole32 = oledll.ole32
_oleaut32 = WinDLL('oleaut32') _oleaut32 = WinDLL('oleaut32')
_CLSIDFromString = _ole32.CLSIDFromString _CLSIDFromString = _ole32.CLSIDFromString
_CoInitialize = _ole32.CoInitialize _CoInitialize = _ole32.CoInitialize
_CoInitialize.argtypes = [c_void_p] _CoInitialize.argtypes = [c_void_p]
_CoCreateInstance = _ole32.CoCreateInstance _CoCreateInstance = _ole32.CoCreateInstance
_SysAllocString = _oleaut32.SysAllocString _SysAllocString = _oleaut32.SysAllocString
_SysAllocString.restype = c_void_p _SysAllocString.restype = c_void_p
_SysAllocString.argtypes = [c_wchar_p] _SysAllocString.argtypes = [c_wchar_p]
_SysFreeString = _oleaut32.SysFreeString _SysFreeString = _oleaut32.SysFreeString
_SysFreeString.argtypes = [c_void_p] _SysFreeString.argtypes = [c_void_p]
# SAFEARRAY* # SAFEARRAY*
# SafeArrayCreateVector(_In_ VARTYPE vt,_In_ LONG lLbound,_In_ ULONG # SafeArrayCreateVector(_In_ VARTYPE vt,_In_ LONG lLbound,_In_ ULONG
# cElements); # cElements);
_SafeArrayCreateVector = _oleaut32.SafeArrayCreateVector _SafeArrayCreateVector = _oleaut32.SafeArrayCreateVector
_SafeArrayCreateVector.restype = c_void_p _SafeArrayCreateVector.restype = c_void_p
_SafeArrayCreateVector.argtypes = [c_ushort, c_long, c_ulong] _SafeArrayCreateVector.argtypes = [c_ushort, c_long, c_ulong]
# HRESULT # HRESULT
# SafeArrayAccessData(_In_ SAFEARRAY *psa, _Out_ void **ppvData); # SafeArrayAccessData(_In_ SAFEARRAY *psa, _Out_ void **ppvData);
_SafeArrayAccessData = _oleaut32.SafeArrayAccessData _SafeArrayAccessData = _oleaut32.SafeArrayAccessData
_SafeArrayAccessData.argtypes = [c_void_p, POINTER(c_void_p)] _SafeArrayAccessData.argtypes = [c_void_p, POINTER(c_void_p)]
# HRESULT # HRESULT
# SafeArrayUnaccessData(_In_ SAFEARRAY *psa); # SafeArrayUnaccessData(_In_ SAFEARRAY *psa);
_SafeArrayUnaccessData = _oleaut32.SafeArrayUnaccessData _SafeArrayUnaccessData = _oleaut32.SafeArrayUnaccessData
_SafeArrayUnaccessData.argtypes = [c_void_p] _SafeArrayUnaccessData.argtypes = [c_void_p]
# HRESULT # HRESULT
# SafeArrayGetUBound(_In_ SAFEARRAY *psa, _In_ UINT nDim, _Out_ LONG # SafeArrayGetUBound(_In_ SAFEARRAY *psa, _In_ UINT nDim, _Out_ LONG
# *plUbound); # *plUbound);
_SafeArrayGetUBound = _oleaut32.SafeArrayGetUBound _SafeArrayGetUBound = _oleaut32.SafeArrayGetUBound
_SafeArrayGetUBound.argtypes = [c_void_p, c_ulong, POINTER(c_long)] _SafeArrayGetUBound.argtypes = [c_void_p, c_ulong, POINTER(c_long)]
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
class BSTR(c_wchar_p): class BSTR(c_wchar_p):
''' BSTR class in python. ''' ''' BSTR class in python. '''
def __init__(self, value): def __init__(self, value):
super(BSTR, self).__init__(_SysAllocString(value)) super(BSTR, self).__init__(_SysAllocString(value))
def __del__(self): def __del__(self):
_SysFreeString(self) _SysFreeString(self)
class VARIANT(Structure): class VARIANT(Structure):
''' '''
VARIANT structure in python. Does not match the definition in VARIANT structure in python. Does not match the definition in
MSDN exactly & it is only mapping the used fields. Field names are also MSDN exactly & it is only mapping the used fields. Field names are also
slighty different. slighty different.
''' '''
class _tagData(Union): class _tagData(Union):
class _tagRecord(Structure): class _tagRecord(Structure):
_fields_ = [('pvoid', c_void_p), ('precord', c_void_p)] _fields_ = [('pvoid', c_void_p), ('precord', c_void_p)]
_fields_ = [('llval', c_longlong), _fields_ = [('llval', c_longlong),
('ullval', c_ulonglong), ('ullval', c_ulonglong),
('lval', c_long), ('lval', c_long),
('ulval', c_ulong), ('ulval', c_ulong),
('ival', c_short), ('ival', c_short),
('boolval', c_ushort), ('boolval', c_ushort),
('bstrval', BSTR), ('bstrval', BSTR),
('parray', c_void_p), ('parray', c_void_p),
('record', _tagRecord)] ('record', _tagRecord)]
_fields_ = [('vt', c_ushort), _fields_ = [('vt', c_ushort),
('wReserved1', c_ushort), ('wReserved1', c_ushort),
('wReserved2', c_ushort), ('wReserved2', c_ushort),
('wReserved3', c_ushort), ('wReserved3', c_ushort),
('vdata', _tagData)] ('vdata', _tagData)]
@staticmethod @staticmethod
def create_empty(): def create_empty():
variant = VARIANT() variant = VARIANT()
variant.vt = VT_EMPTY variant.vt = VT_EMPTY
variant.vdata.llval = 0 variant.vdata.llval = 0
return variant return variant
@staticmethod @staticmethod
def create_safearray_from_str(text): def create_safearray_from_str(text):
variant = VARIANT() variant = VARIANT()
variant.vt = VT_ARRAY | VT_UI1 variant.vt = VT_ARRAY | VT_UI1
length = len(text) length = len(text)
variant.vdata.parray = _SafeArrayCreateVector(VT_UI1, 0, length) variant.vdata.parray = _SafeArrayCreateVector(VT_UI1, 0, length)
pvdata = c_void_p() pvdata = c_void_p()
_SafeArrayAccessData(variant.vdata.parray, byref(pvdata)) _SafeArrayAccessData(variant.vdata.parray, byref(pvdata))
ctypes.memmove(pvdata, text, length) ctypes.memmove(pvdata, text, length)
_SafeArrayUnaccessData(variant.vdata.parray) _SafeArrayUnaccessData(variant.vdata.parray)
return variant return variant
@staticmethod @staticmethod
def create_bstr_from_str(text): def create_bstr_from_str(text):
variant = VARIANT() variant = VARIANT()
variant.vt = VT_BSTR variant.vt = VT_BSTR
variant.vdata.bstrval = BSTR(text) variant.vdata.bstrval = BSTR(text)
return variant return variant
@staticmethod @staticmethod
def create_bool_false(): def create_bool_false():
variant = VARIANT() variant = VARIANT()
variant.vt = VT_BOOL variant.vt = VT_BOOL
variant.vdata.boolval = 0 variant.vdata.boolval = 0
return variant return variant
def is_safearray_of_bytes(self): def is_safearray_of_bytes(self):
return self.vt == VT_ARRAY | VT_UI1 return self.vt == VT_ARRAY | VT_UI1
def str_from_safearray(self): def str_from_safearray(self):
assert self.vt == VT_ARRAY | VT_UI1 assert self.vt == VT_ARRAY | VT_UI1
pvdata = c_void_p() pvdata = c_void_p()
count = c_long() count = c_long()
_SafeArrayGetUBound(self.vdata.parray, 1, byref(count)) _SafeArrayGetUBound(self.vdata.parray, 1, byref(count))
count = c_long(count.value + 1) count = c_long(count.value + 1)
_SafeArrayAccessData(self.vdata.parray, byref(pvdata)) _SafeArrayAccessData(self.vdata.parray, byref(pvdata))
text = ctypes.string_at(pvdata, count) text = ctypes.string_at(pvdata, count)
_SafeArrayUnaccessData(self.vdata.parray) _SafeArrayUnaccessData(self.vdata.parray)
return text return text
def __del__(self): def __del__(self):
_VariantClear(self) _VariantClear(self)
# HRESULT VariantClear(_Inout_ VARIANTARG *pvarg); # HRESULT VariantClear(_Inout_ VARIANTARG *pvarg);
_VariantClear = _oleaut32.VariantClear _VariantClear = _oleaut32.VariantClear
_VariantClear.argtypes = [POINTER(VARIANT)] _VariantClear.argtypes = [POINTER(VARIANT)]
class GUID(Structure): class GUID(Structure):
''' GUID structure in python. ''' ''' GUID structure in python. '''
_fields_ = [("data1", c_ulong), _fields_ = [("data1", c_ulong),
("data2", c_ushort), ("data2", c_ushort),
("data3", c_ushort), ("data3", c_ushort),
("data4", c_byte * 8)] ("data4", c_byte * 8)]
def __init__(self, name=None): def __init__(self, name=None):
if name is not None: if name is not None:
_CLSIDFromString(unicode(name), byref(self)) _CLSIDFromString(unicode(name), byref(self))
class _WinHttpRequest(c_void_p): class _WinHttpRequest(c_void_p):
''' '''
Maps the Com API to Python class functions. Not all methods in Maps the Com API to Python class functions. Not all methods in
IWinHttpWebRequest are mapped - only the methods we use. IWinHttpWebRequest are mapped - only the methods we use.
''' '''
_AddRef = WINFUNCTYPE(c_long) \ _AddRef = WINFUNCTYPE(c_long) \
(1, 'AddRef') (1, 'AddRef')
_Release = WINFUNCTYPE(c_long) \ _Release = WINFUNCTYPE(c_long) \
(2, 'Release') (2, 'Release')
_SetProxy = WINFUNCTYPE(HRESULT, _SetProxy = WINFUNCTYPE(HRESULT,
HTTPREQUEST_PROXY_SETTING, HTTPREQUEST_PROXY_SETTING,
VARIANT, VARIANT,
VARIANT) \ VARIANT) \
(7, 'SetProxy') (7, 'SetProxy')
_SetCredentials = WINFUNCTYPE(HRESULT, _SetCredentials = WINFUNCTYPE(HRESULT,
BSTR, BSTR,
BSTR, BSTR,
HTTPREQUEST_SETCREDENTIALS_FLAGS) \ HTTPREQUEST_SETCREDENTIALS_FLAGS) \
(8, 'SetCredentials') (8, 'SetCredentials')
_Open = WINFUNCTYPE(HRESULT, BSTR, BSTR, VARIANT) \ _Open = WINFUNCTYPE(HRESULT, BSTR, BSTR, VARIANT) \
(9, 'Open') (9, 'Open')
_SetRequestHeader = WINFUNCTYPE(HRESULT, BSTR, BSTR) \ _SetRequestHeader = WINFUNCTYPE(HRESULT, BSTR, BSTR) \
(10, 'SetRequestHeader') (10, 'SetRequestHeader')
_GetResponseHeader = WINFUNCTYPE(HRESULT, BSTR, POINTER(c_void_p)) \ _GetResponseHeader = WINFUNCTYPE(HRESULT, BSTR, POINTER(c_void_p)) \
(11, 'GetResponseHeader') (11, 'GetResponseHeader')
_GetAllResponseHeaders = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \ _GetAllResponseHeaders = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
(12, 'GetAllResponseHeaders') (12, 'GetAllResponseHeaders')
_Send = WINFUNCTYPE(HRESULT, VARIANT) \ _Send = WINFUNCTYPE(HRESULT, VARIANT) \
(13, 'Send') (13, 'Send')
_Status = WINFUNCTYPE(HRESULT, POINTER(c_long)) \ _Status = WINFUNCTYPE(HRESULT, POINTER(c_long)) \
(14, 'Status') (14, 'Status')
_StatusText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \ _StatusText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
(15, 'StatusText') (15, 'StatusText')
_ResponseText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \ _ResponseText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
(16, 'ResponseText') (16, 'ResponseText')
_ResponseBody = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \ _ResponseBody = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \
(17, 'ResponseBody') (17, 'ResponseBody')
_ResponseStream = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \ _ResponseStream = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \
(18, 'ResponseStream') (18, 'ResponseStream')
_WaitForResponse = WINFUNCTYPE(HRESULT, VARIANT, POINTER(c_ushort)) \ _WaitForResponse = WINFUNCTYPE(HRESULT, VARIANT, POINTER(c_ushort)) \
(21, 'WaitForResponse') (21, 'WaitForResponse')
_Abort = WINFUNCTYPE(HRESULT) \ _Abort = WINFUNCTYPE(HRESULT) \
(22, 'Abort') (22, 'Abort')
_SetTimeouts = WINFUNCTYPE(HRESULT, c_long, c_long, c_long, c_long) \ _SetTimeouts = WINFUNCTYPE(HRESULT, c_long, c_long, c_long, c_long) \
(23, 'SetTimeouts') (23, 'SetTimeouts')
_SetClientCertificate = WINFUNCTYPE(HRESULT, BSTR) \ _SetClientCertificate = WINFUNCTYPE(HRESULT, BSTR) \
(24, 'SetClientCertificate') (24, 'SetClientCertificate')
def open(self, method, url): def open(self, method, url):
''' '''
Opens the request. Opens the request.
method: the request VERB 'GET', 'POST', etc. method: the request VERB 'GET', 'POST', etc.
url: the url to connect url: the url to connect
''' '''
_WinHttpRequest._SetTimeouts(self, 0, 65000, 65000, 65000) _WinHttpRequest._SetTimeouts(self, 0, 65000, 65000, 65000)
flag = VARIANT.create_bool_false() flag = VARIANT.create_bool_false()
_method = BSTR(method) _method = BSTR(method)
_url = BSTR(url) _url = BSTR(url)
_WinHttpRequest._Open(self, _method, _url, flag) _WinHttpRequest._Open(self, _method, _url, flag)
def set_request_header(self, name, value): def set_request_header(self, name, value):
''' Sets the request header. ''' ''' Sets the request header. '''
_name = BSTR(name) _name = BSTR(name)
_value = BSTR(value) _value = BSTR(value)
_WinHttpRequest._SetRequestHeader(self, _name, _value) _WinHttpRequest._SetRequestHeader(self, _name, _value)
def get_all_response_headers(self): def get_all_response_headers(self):
''' Gets back all response headers. ''' ''' Gets back all response headers. '''
bstr_headers = c_void_p() bstr_headers = c_void_p()
_WinHttpRequest._GetAllResponseHeaders(self, byref(bstr_headers)) _WinHttpRequest._GetAllResponseHeaders(self, byref(bstr_headers))
bstr_headers = ctypes.cast(bstr_headers, c_wchar_p) bstr_headers = ctypes.cast(bstr_headers, c_wchar_p)
headers = bstr_headers.value headers = bstr_headers.value
_SysFreeString(bstr_headers) _SysFreeString(bstr_headers)
return headers return headers
def send(self, request=None): def send(self, request=None):
''' Sends the request body. ''' ''' Sends the request body. '''
# Sends VT_EMPTY if it is GET, HEAD request. # Sends VT_EMPTY if it is GET, HEAD request.
if request is None: if request is None:
var_empty = VARIANT.create_empty() var_empty = VARIANT.create_empty()
_WinHttpRequest._Send(self, var_empty) _WinHttpRequest._Send(self, var_empty)
else: # Sends request body as SAFEArray. else: # Sends request body as SAFEArray.
_request = VARIANT.create_safearray_from_str(request) _request = VARIANT.create_safearray_from_str(request)
_WinHttpRequest._Send(self, _request) _WinHttpRequest._Send(self, _request)
def status(self): def status(self):
''' Gets status of response. ''' ''' Gets status of response. '''
status = c_long() status = c_long()
_WinHttpRequest._Status(self, byref(status)) _WinHttpRequest._Status(self, byref(status))
return int(status.value) return int(status.value)
def status_text(self): def status_text(self):
''' Gets status text of response. ''' ''' Gets status text of response. '''
bstr_status_text = c_void_p() bstr_status_text = c_void_p()
_WinHttpRequest._StatusText(self, byref(bstr_status_text)) _WinHttpRequest._StatusText(self, byref(bstr_status_text))
bstr_status_text = ctypes.cast(bstr_status_text, c_wchar_p) bstr_status_text = ctypes.cast(bstr_status_text, c_wchar_p)
status_text = bstr_status_text.value status_text = bstr_status_text.value
_SysFreeString(bstr_status_text) _SysFreeString(bstr_status_text)
return status_text return status_text
def response_body(self): def response_body(self):
''' '''
Gets response body as a SAFEARRAY and converts the SAFEARRAY to str. Gets response body as a SAFEARRAY and converts the SAFEARRAY to str.
If it is an xml file, it always contains 3 characters before <?xml, If it is an xml file, it always contains 3 characters before <?xml,
so we remove them. so we remove them.
''' '''
var_respbody = VARIANT() var_respbody = VARIANT()
_WinHttpRequest._ResponseBody(self, byref(var_respbody)) _WinHttpRequest._ResponseBody(self, byref(var_respbody))
if var_respbody.is_safearray_of_bytes(): if var_respbody.is_safearray_of_bytes():
respbody = var_respbody.str_from_safearray() respbody = var_respbody.str_from_safearray()
if respbody[3:].startswith(b'<?xml') and\ if respbody[3:].startswith(b'<?xml') and\
respbody.startswith(b'\xef\xbb\xbf'): respbody.startswith(b'\xef\xbb\xbf'):
respbody = respbody[3:] respbody = respbody[3:]
return respbody return respbody
else: else:
return '' return ''
def set_client_certificate(self, certificate): def set_client_certificate(self, certificate):
'''Sets client certificate for the request. ''' '''Sets client certificate for the request. '''
_certificate = BSTR(certificate) _certificate = BSTR(certificate)
_WinHttpRequest._SetClientCertificate(self, _certificate) _WinHttpRequest._SetClientCertificate(self, _certificate)
def set_tunnel(self, host, port): def set_tunnel(self, host, port):
''' Sets up the host and the port for the HTTP CONNECT Tunnelling.''' ''' Sets up the host and the port for the HTTP CONNECT Tunnelling.'''
url = host url = host
if port: if port:
url = url + u':' + port url = url + u':' + port
var_host = VARIANT.create_bstr_from_str(url) var_host = VARIANT.create_bstr_from_str(url)
var_empty = VARIANT.create_empty() var_empty = VARIANT.create_empty()
_WinHttpRequest._SetProxy( _WinHttpRequest._SetProxy(
self, HTTPREQUEST_PROXYSETTING_PROXY, var_host, var_empty) self, HTTPREQUEST_PROXYSETTING_PROXY, var_host, var_empty)
def set_proxy_credentials(self, user, password): def set_proxy_credentials(self, user, password):
_WinHttpRequest._SetCredentials( _WinHttpRequest._SetCredentials(
self, BSTR(user), BSTR(password), self, BSTR(user), BSTR(password),
HTTPREQUEST_SETCREDENTIALS_FOR_PROXY) HTTPREQUEST_SETCREDENTIALS_FOR_PROXY)
def __del__(self): def __del__(self):
if self.value is not None: if self.value is not None:
_WinHttpRequest._Release(self) _WinHttpRequest._Release(self)
class _Response(object): class _Response(object):
''' Response class corresponding to the response returned from httplib ''' Response class corresponding to the response returned from httplib
HTTPConnection. ''' HTTPConnection. '''
def __init__(self, _status, _status_text, _length, _headers, _respbody): def __init__(self, _status, _status_text, _length, _headers, _respbody):
self.status = _status self.status = _status
self.reason = _status_text self.reason = _status_text
self.length = _length self.length = _length
self.headers = _headers self.headers = _headers
self.respbody = _respbody self.respbody = _respbody
def getheaders(self): def getheaders(self):
'''Returns response headers.''' '''Returns response headers.'''
return self.headers return self.headers
def read(self, _length): def read(self, _length):
'''Returns resonse body. ''' '''Returns resonse body. '''
return self.respbody[:_length] return self.respbody[:_length]
class _HTTPConnection(object): class _HTTPConnection(object):
''' Class corresponding to httplib HTTPConnection class. ''' ''' Class corresponding to httplib HTTPConnection class. '''
def __init__(self, host, cert_file=None, key_file=None, protocol='http'): def __init__(self, host, cert_file=None, key_file=None, protocol='http'):
''' initialize the IWinHttpWebRequest Com Object.''' ''' initialize the IWinHttpWebRequest Com Object.'''
self.host = unicode(host) self.host = unicode(host)
self.cert_file = cert_file self.cert_file = cert_file
self._httprequest = _WinHttpRequest() self._httprequest = _WinHttpRequest()
self.protocol = protocol self.protocol = protocol
clsid = GUID('{2087C2F4-2CEF-4953-A8AB-66779B670495}') clsid = GUID('{2087C2F4-2CEF-4953-A8AB-66779B670495}')
iid = GUID('{016FE2EC-B2C8-45F8-B23B-39E53A75396B}') iid = GUID('{016FE2EC-B2C8-45F8-B23B-39E53A75396B}')
_CoInitialize(None) _CoInitialize(None)
_CoCreateInstance(byref(clsid), 0, 1, byref(iid), _CoCreateInstance(byref(clsid), 0, 1, byref(iid),
byref(self._httprequest)) byref(self._httprequest))
def close(self): def close(self):
pass pass
def set_tunnel(self, host, port=None, headers=None): def set_tunnel(self, host, port=None, headers=None):
''' Sets up the host and the port for the HTTP CONNECT Tunnelling. ''' ''' Sets up the host and the port for the HTTP CONNECT Tunnelling. '''
self._httprequest.set_tunnel(unicode(host), unicode(str(port))) self._httprequest.set_tunnel(unicode(host), unicode(str(port)))
def set_proxy_credentials(self, user, password): def set_proxy_credentials(self, user, password):
self._httprequest.set_proxy_credentials( self._httprequest.set_proxy_credentials(
unicode(user), unicode(password)) unicode(user), unicode(password))
def putrequest(self, method, uri): def putrequest(self, method, uri):
''' Connects to host and sends the request. ''' ''' Connects to host and sends the request. '''
protocol = unicode(self.protocol + '://') protocol = unicode(self.protocol + '://')
url = protocol + self.host + unicode(uri) url = protocol + self.host + unicode(uri)
self._httprequest.open(unicode(method), url) self._httprequest.open(unicode(method), url)
# sets certificate for the connection if cert_file is set. # sets certificate for the connection if cert_file is set.
if self.cert_file is not None: if self.cert_file is not None:
self._httprequest.set_client_certificate(unicode(self.cert_file)) self._httprequest.set_client_certificate(unicode(self.cert_file))
def putheader(self, name, value): def putheader(self, name, value):
''' Sends the headers of request. ''' ''' Sends the headers of request. '''
if sys.version_info < (3,): if sys.version_info < (3,):
name = str(name).decode('utf-8') name = str(name).decode('utf-8')
value = str(value).decode('utf-8') value = str(value).decode('utf-8')
self._httprequest.set_request_header(name, value) self._httprequest.set_request_header(name, value)
def endheaders(self): def endheaders(self):
''' No operation. Exists only to provide the same interface of httplib ''' No operation. Exists only to provide the same interface of httplib
HTTPConnection.''' HTTPConnection.'''
pass pass
def send(self, request_body): def send(self, request_body):
''' Sends request body. ''' ''' Sends request body. '''
if not request_body: if not request_body:
self._httprequest.send() self._httprequest.send()
else: else:
self._httprequest.send(request_body) self._httprequest.send(request_body)
def getresponse(self): def getresponse(self):
''' Gets the response and generates the _Response object''' ''' Gets the response and generates the _Response object'''
status = self._httprequest.status() status = self._httprequest.status()
status_text = self._httprequest.status_text() status_text = self._httprequest.status_text()
resp_headers = self._httprequest.get_all_response_headers() resp_headers = self._httprequest.get_all_response_headers()
fixed_headers = [] fixed_headers = []
for resp_header in resp_headers.split('\n'): for resp_header in resp_headers.split('\n'):
if (resp_header.startswith('\t') or\ if (resp_header.startswith('\t') or\
resp_header.startswith(' ')) and fixed_headers: resp_header.startswith(' ')) and fixed_headers:
# append to previous header # append to previous header
fixed_headers[-1] += resp_header fixed_headers[-1] += resp_header
else: else:
fixed_headers.append(resp_header) fixed_headers.append(resp_header)
headers = [] headers = []
for resp_header in fixed_headers: for resp_header in fixed_headers:
if ':' in resp_header: if ':' in resp_header:
pos = resp_header.find(':') pos = resp_header.find(':')
headers.append( headers.append(
(resp_header[:pos].lower(), resp_header[pos + 1:].strip())) (resp_header[:pos].lower(), resp_header[pos + 1:].strip()))
body = self._httprequest.response_body() body = self._httprequest.response_body()
length = len(body) length = len(body)
return _Response(status, status_text, length, headers, body) return _Response(status, status_text, length, headers, body)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#--------------------------------------------------------------------------
from azure import (
MANAGEMENT_HOST,
_str
)
from azure.servicemanagement import (
CloudServices,
)
from azure.servicemanagement.servicemanagementclient import (
_ServiceManagementClient,
)
class SchedulerManagementService(_ServiceManagementClient):
''' Note that this class is a preliminary work on Scheduler
management. Since it lack a lot a features, final version
can be slightly different from the current one.
'''
def __init__(self, subscription_id=None, cert_file=None,
host=MANAGEMENT_HOST, request_session=None):
'''
Initializes the scheduler management service.
subscription_id: Subscription to manage.
cert_file:
Path to .pem certificate file (httplib), or location of the
certificate in your Personal certificate store (winhttp) in the
CURRENT_USER\my\CertificateName format.
If a request_session is specified, then this is unused.
host: Live ServiceClient URL. Defaults to Azure public cloud.
request_session:
Session object to use for http requests. If this is specified, it
replaces the default use of httplib or winhttp. Also, the cert_file
parameter is unused when a session is passed in.
The session object handles authentication, and as such can support
multiple types of authentication: .pem certificate, oauth.
For example, you can pass in a Session instance from the requests
library. To use .pem certificate authentication with requests
library, set the path to the .pem file on the session.cert
attribute.
'''
super(SchedulerManagementService, self).__init__(
subscription_id, cert_file, host, request_session)
#--Operations for scheduler ----------------------------------------
def list_cloud_services(self):
'''
List the cloud services for scheduling defined on the account.
'''
return self._perform_get(self._get_list_cloud_services_path(),
CloudServices)
#--Helper functions --------------------------------------------------
def _get_list_cloud_services_path(self):
return self._get_path('cloudservices', None)

View File

@@ -1,113 +1,534 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
from azure import ( from azure import (
MANAGEMENT_HOST, MANAGEMENT_HOST,
_convert_response_to_feeds, _convert_response_to_feeds,
_str, _str,
_validate_not_none, _validate_not_none,
) _convert_xml_to_windows_azure_object,
from azure.servicemanagement import ( )
_ServiceBusManagementXmlSerializer, from azure.servicemanagement import (
) _ServiceBusManagementXmlSerializer,
from azure.servicemanagement.servicemanagementclient import ( QueueDescription,
_ServiceManagementClient, TopicDescription,
) NotificationHubDescription,
RelayDescription,
MetricProperties,
class ServiceBusManagementService(_ServiceManagementClient): MetricValues,
MetricRollups,
def __init__(self, subscription_id=None, cert_file=None, )
host=MANAGEMENT_HOST): from azure.servicemanagement.servicemanagementclient import (
super(ServiceBusManagementService, self).__init__( _ServiceManagementClient,
subscription_id, cert_file, host) )
#--Operations for service bus ---------------------------------------- from functools import partial
def get_regions(self):
''' X_MS_VERSION = '2012-03-01'
Get list of available service bus regions.
''' class ServiceBusManagementService(_ServiceManagementClient):
response = self._perform_get(
self._get_path('services/serviceBus/Regions/', None), def __init__(self, subscription_id=None, cert_file=None,
None) host=MANAGEMENT_HOST, request_session=None):
'''
return _convert_response_to_feeds( Initializes the service bus management service.
response,
_ServiceBusManagementXmlSerializer.xml_to_region) subscription_id: Subscription to manage.
cert_file:
def list_namespaces(self): Path to .pem certificate file (httplib), or location of the
''' certificate in your Personal certificate store (winhttp) in the
List the service bus namespaces defined on the account. CURRENT_USER\my\CertificateName format.
''' If a request_session is specified, then this is unused.
response = self._perform_get( host: Live ServiceClient URL. Defaults to Azure public cloud.
self._get_path('services/serviceBus/Namespaces/', None), request_session:
None) Session object to use for http requests. If this is specified, it
replaces the default use of httplib or winhttp. Also, the cert_file
return _convert_response_to_feeds( parameter is unused when a session is passed in.
response, The session object handles authentication, and as such can support
_ServiceBusManagementXmlSerializer.xml_to_namespace) multiple types of authentication: .pem certificate, oauth.
For example, you can pass in a Session instance from the requests
def get_namespace(self, name): library. To use .pem certificate authentication with requests
''' library, set the path to the .pem file on the session.cert
Get details about a specific namespace. attribute.
'''
name: Name of the service bus namespace. super(ServiceBusManagementService, self).__init__(
''' subscription_id, cert_file, host, request_session)
response = self._perform_get( self.x_ms_version = X_MS_VERSION
self._get_path('services/serviceBus/Namespaces', name),
None) # Operations for service bus ----------------------------------------
def get_regions(self):
return _ServiceBusManagementXmlSerializer.xml_to_namespace( '''
response.body) Get list of available service bus regions.
'''
def create_namespace(self, name, region): response = self._perform_get(
''' self._get_path('services/serviceBus/Regions/', None),
Create a new service bus namespace. None)
name: Name of the service bus namespace to create. return _convert_response_to_feeds(
region: Region to create the namespace in. response,
''' _ServiceBusManagementXmlSerializer.xml_to_region)
_validate_not_none('name', name)
def list_namespaces(self):
return self._perform_put( '''
self._get_path('services/serviceBus/Namespaces', name), List the service bus namespaces defined on the account.
_ServiceBusManagementXmlSerializer.namespace_to_xml(region)) '''
response = self._perform_get(
def delete_namespace(self, name): self._get_path('services/serviceBus/Namespaces/', None),
''' None)
Delete a service bus namespace.
return _convert_response_to_feeds(
name: Name of the service bus namespace to delete. response,
''' _ServiceBusManagementXmlSerializer.xml_to_namespace)
_validate_not_none('name', name)
def get_namespace(self, name):
return self._perform_delete( '''
self._get_path('services/serviceBus/Namespaces', name), Get details about a specific namespace.
None)
name: Name of the service bus namespace.
def check_namespace_availability(self, name): '''
''' response = self._perform_get(
Checks to see if the specified service bus namespace is available, or self._get_path('services/serviceBus/Namespaces', name),
if it has already been taken. None)
name: Name of the service bus namespace to validate. return _ServiceBusManagementXmlSerializer.xml_to_namespace(
''' response.body)
_validate_not_none('name', name)
def create_namespace(self, name, region):
response = self._perform_get( '''
self._get_path('services/serviceBus/CheckNamespaceAvailability', Create a new service bus namespace.
None) + '/?namespace=' + _str(name), None)
name: Name of the service bus namespace to create.
return _ServiceBusManagementXmlSerializer.xml_to_namespace_availability( region: Region to create the namespace in.
response.body) '''
_validate_not_none('name', name)
return self._perform_put(
self._get_path('services/serviceBus/Namespaces', name),
_ServiceBusManagementXmlSerializer.namespace_to_xml(region))
def delete_namespace(self, name):
'''
Delete a service bus namespace.
name: Name of the service bus namespace to delete.
'''
_validate_not_none('name', name)
return self._perform_delete(
self._get_path('services/serviceBus/Namespaces', name),
None)
def check_namespace_availability(self, name):
'''
Checks to see if the specified service bus namespace is available, or
if it has already been taken.
name: Name of the service bus namespace to validate.
'''
_validate_not_none('name', name)
response = self._perform_get(
self._get_path('services/serviceBus/CheckNamespaceAvailability',
None) + '/?namespace=' + _str(name), None)
return _ServiceBusManagementXmlSerializer.xml_to_namespace_availability(
response.body)
def list_queues(self, name):
'''
Enumerates the queues in the service namespace.
name: Name of the service bus namespace.
'''
_validate_not_none('name', name)
response = self._perform_get(
self._get_list_queues_path(name),
None)
return _convert_response_to_feeds(response,
partial(_convert_xml_to_windows_azure_object,
azure_type=QueueDescription))
def list_topics(self, name):
'''
Retrieves the topics in the service namespace.
name: Name of the service bus namespace.
'''
response = self._perform_get(
self._get_list_topics_path(name),
None)
return _convert_response_to_feeds(response,
partial(_convert_xml_to_windows_azure_object,
azure_type=TopicDescription))
def list_notification_hubs(self, name):
'''
Retrieves the notification hubs in the service namespace.
name: Name of the service bus namespace.
'''
response = self._perform_get(
self._get_list_notification_hubs_path(name),
None)
return _convert_response_to_feeds(response,
partial(_convert_xml_to_windows_azure_object,
azure_type=NotificationHubDescription))
def list_relays(self, name):
'''
Retrieves the relays in the service namespace.
name: Name of the service bus namespace.
'''
response = self._perform_get(
self._get_list_relays_path(name),
None)
return _convert_response_to_feeds(response,
partial(_convert_xml_to_windows_azure_object,
azure_type=RelayDescription))
def get_supported_metrics_queue(self, name, queue_name):
'''
Retrieves the list of supported metrics for this namespace and queue
name: Name of the service bus namespace.
queue_name: Name of the service bus queue in this namespace.
'''
response = self._perform_get(
self._get_get_supported_metrics_queue_path(name, queue_name),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricProperties))
def get_supported_metrics_topic(self, name, topic_name):
'''
Retrieves the list of supported metrics for this namespace and topic
name: Name of the service bus namespace.
topic_name: Name of the service bus queue in this namespace.
'''
response = self._perform_get(
self._get_get_supported_metrics_topic_path(name, topic_name),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricProperties))
def get_supported_metrics_notification_hub(self, name, hub_name):
'''
Retrieves the list of supported metrics for this namespace and topic
name: Name of the service bus namespace.
hub_name: Name of the service bus notification hub in this namespace.
'''
response = self._perform_get(
self._get_get_supported_metrics_hub_path(name, hub_name),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricProperties))
def get_supported_metrics_relay(self, name, relay_name):
'''
Retrieves the list of supported metrics for this namespace and relay
name: Name of the service bus namespace.
relay_name: Name of the service bus relay in this namespace.
'''
response = self._perform_get(
self._get_get_supported_metrics_relay_path(name, relay_name),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricProperties))
def get_metrics_data_queue(self, name, queue_name, metric, rollup, filter_expresssion):
'''
Retrieves the list of supported metrics for this namespace and queue
name: Name of the service bus namespace.
queue_name: Name of the service bus queue in this namespace.
metric: name of a supported metric
rollup: name of a supported rollup
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
'''
response = self._perform_get(
self._get_get_metrics_data_queue_path(name, queue_name, metric, rollup, filter_expresssion),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricValues))
def get_metrics_data_topic(self, name, topic_name, metric, rollup, filter_expresssion):
'''
Retrieves the list of supported metrics for this namespace and topic
name: Name of the service bus namespace.
topic_name: Name of the service bus queue in this namespace.
metric: name of a supported metric
rollup: name of a supported rollup
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
'''
response = self._perform_get(
self._get_get_metrics_data_topic_path(name, topic_name, metric, rollup, filter_expresssion),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricValues))
def get_metrics_data_notification_hub(self, name, hub_name, metric, rollup, filter_expresssion):
'''
Retrieves the list of supported metrics for this namespace and topic
name: Name of the service bus namespace.
hub_name: Name of the service bus notification hub in this namespace.
metric: name of a supported metric
rollup: name of a supported rollup
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
'''
response = self._perform_get(
self._get_get_metrics_data_hub_path(name, hub_name, metric, rollup, filter_expresssion),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricValues))
def get_metrics_data_relay(self, name, relay_name, metric, rollup, filter_expresssion):
'''
Retrieves the list of supported metrics for this namespace and relay
name: Name of the service bus namespace.
relay_name: Name of the service bus relay in this namespace.
metric: name of a supported metric
rollup: name of a supported rollup
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
'''
response = self._perform_get(
self._get_get_metrics_data_relay_path(name, relay_name, metric, rollup, filter_expresssion),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricValues))
def get_metrics_rollups_queue(self, name, queue_name, metric):
'''
This operation gets rollup data for Service Bus metrics queue.
Rollup data includes the time granularity for the telemetry aggregation as well as
the retention settings for each time granularity.
name: Name of the service bus namespace.
queue_name: Name of the service bus queue in this namespace.
metric: name of a supported metric
'''
response = self._perform_get(
self._get_get_metrics_rollup_queue_path(name, queue_name, metric),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricRollups))
def get_metrics_rollups_topic(self, name, topic_name, metric):
'''
This operation gets rollup data for Service Bus metrics topic.
Rollup data includes the time granularity for the telemetry aggregation as well as
the retention settings for each time granularity.
name: Name of the service bus namespace.
topic_name: Name of the service bus queue in this namespace.
metric: name of a supported metric
'''
response = self._perform_get(
self._get_get_metrics_rollup_topic_path(name, topic_name, metric),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricRollups))
def get_metrics_rollups_notification_hub(self, name, hub_name, metric):
'''
This operation gets rollup data for Service Bus metrics notification hub.
Rollup data includes the time granularity for the telemetry aggregation as well as
the retention settings for each time granularity.
name: Name of the service bus namespace.
hub_name: Name of the service bus notification hub in this namespace.
metric: name of a supported metric
'''
response = self._perform_get(
self._get_get_metrics_rollup_hub_path(name, hub_name, metric),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricRollups))
def get_metrics_rollups_relay(self, name, relay_name, metric):
'''
This operation gets rollup data for Service Bus metrics relay.
Rollup data includes the time granularity for the telemetry aggregation as well as
the retention settings for each time granularity.
name: Name of the service bus namespace.
relay_name: Name of the service bus relay in this namespace.
metric: name of a supported metric
'''
response = self._perform_get(
self._get_get_metrics_rollup_relay_path(name, relay_name, metric),
None)
return _convert_response_to_feeds(response,
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
object_type=MetricRollups))
# Helper functions --------------------------------------------------
def _get_list_queues_path(self, namespace_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/Queues'
def _get_list_topics_path(self, namespace_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/Topics'
def _get_list_notification_hubs_path(self, namespace_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/NotificationHubs'
def _get_list_relays_path(self, namespace_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/Relays'
def _get_get_supported_metrics_queue_path(self, namespace_name, queue_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/Queues/' + _str(queue_name) + '/Metrics'
def _get_get_supported_metrics_topic_path(self, namespace_name, topic_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/Topics/' + _str(topic_name) + '/Metrics'
def _get_get_supported_metrics_hub_path(self, namespace_name, hub_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/NotificationHubs/' + _str(hub_name) + '/Metrics'
def _get_get_supported_metrics_relay_path(self, namespace_name, queue_name):
return self._get_path('services/serviceBus/Namespaces/',
namespace_name) + '/Relays/' + _str(queue_name) + '/Metrics'
def _get_get_metrics_data_queue_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/Queues/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups/',
_str(rollup),
'/Values?',
filter_expr
])
def _get_get_metrics_data_topic_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/Topics/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups/',
_str(rollup),
'/Values?',
filter_expr
])
def _get_get_metrics_data_hub_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/NotificationHubs/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups/',
_str(rollup),
'/Values?',
filter_expr
])
def _get_get_metrics_data_relay_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/Relays/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups/',
_str(rollup),
'/Values?',
filter_expr
])
def _get_get_metrics_rollup_queue_path(self, namespace_name, queue_name, metric):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/Queues/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups',
])
def _get_get_metrics_rollup_topic_path(self, namespace_name, queue_name, metric):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/Topics/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups',
])
def _get_get_metrics_rollup_hub_path(self, namespace_name, queue_name, metric):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/NotificationHubs/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups',
])
def _get_get_metrics_rollup_relay_path(self, namespace_name, queue_name, metric):
return "".join([
self._get_path('services/serviceBus/Namespaces/', namespace_name),
'/Relays/',
_str(queue_name),
'/Metrics/',
_str(metric),
'/Rollups',
])

View File

@@ -1,166 +1,258 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
import os import os
from azure import ( from azure import (
WindowsAzureError, WindowsAzureError,
MANAGEMENT_HOST, MANAGEMENT_HOST,
_get_request_body, _get_request_body,
_parse_response, _parse_response,
_str, _str,
_update_request_uri_query, _update_request_uri_query,
) )
from azure.http import ( from azure.http import (
HTTPError, HTTPError,
HTTPRequest, HTTPRequest,
) )
from azure.http.httpclient import _HTTPClient from azure.http.httpclient import _HTTPClient
from azure.servicemanagement import ( from azure.servicemanagement import (
AZURE_MANAGEMENT_CERTFILE, AZURE_MANAGEMENT_CERTFILE,
AZURE_MANAGEMENT_SUBSCRIPTIONID, AZURE_MANAGEMENT_SUBSCRIPTIONID,
_management_error_handler, _management_error_handler,
_parse_response_for_async_op, parse_response_for_async_op,
_update_management_header, X_MS_VERSION,
) )
class _ServiceManagementClient(object): class _ServiceManagementClient(object):
def __init__(self, subscription_id=None, cert_file=None, def __init__(self, subscription_id=None, cert_file=None,
host=MANAGEMENT_HOST): host=MANAGEMENT_HOST, request_session=None):
self.requestid = None self.requestid = None
self.subscription_id = subscription_id self.subscription_id = subscription_id
self.cert_file = cert_file self.cert_file = cert_file
self.host = host self.host = host
self.request_session = request_session
if not self.cert_file: self.x_ms_version = X_MS_VERSION
if AZURE_MANAGEMENT_CERTFILE in os.environ: self.content_type = 'application/atom+xml;type=entry;charset=utf-8'
self.cert_file = os.environ[AZURE_MANAGEMENT_CERTFILE]
if not self.cert_file and not request_session:
if not self.subscription_id: if AZURE_MANAGEMENT_CERTFILE in os.environ:
if AZURE_MANAGEMENT_SUBSCRIPTIONID in os.environ: self.cert_file = os.environ[AZURE_MANAGEMENT_CERTFILE]
self.subscription_id = os.environ[
AZURE_MANAGEMENT_SUBSCRIPTIONID] if not self.subscription_id:
if AZURE_MANAGEMENT_SUBSCRIPTIONID in os.environ:
if not self.cert_file or not self.subscription_id: self.subscription_id = os.environ[
raise WindowsAzureError( AZURE_MANAGEMENT_SUBSCRIPTIONID]
'You need to provide subscription id and certificate file')
if not self.request_session:
self._httpclient = _HTTPClient( if not self.cert_file or not self.subscription_id:
service_instance=self, cert_file=self.cert_file) raise WindowsAzureError(
self._filter = self._httpclient.perform_request 'You need to provide subscription id and certificate file')
def with_filter(self, filter): self._httpclient = _HTTPClient(
'''Returns a new service which will process requests with the service_instance=self, cert_file=self.cert_file,
specified filter. Filtering operations can include logging, automatic request_session=self.request_session)
retrying, etc... The filter is a lambda which receives the HTTPRequest self._filter = self._httpclient.perform_request
and another lambda. The filter can perform any pre-processing on the
request, pass it off to the next lambda, and then perform any def with_filter(self, filter):
post-processing on the response.''' '''Returns a new service which will process requests with the
res = type(self)(self.subscription_id, self.cert_file, self.host) specified filter. Filtering operations can include logging, automatic
old_filter = self._filter retrying, etc... The filter is a lambda which receives the HTTPRequest
and another lambda. The filter can perform any pre-processing on the
def new_filter(request): request, pass it off to the next lambda, and then perform any
return filter(request, old_filter) post-processing on the response.'''
res = type(self)(self.subscription_id, self.cert_file, self.host,
res._filter = new_filter self.request_session)
return res old_filter = self._filter
def set_proxy(self, host, port, user=None, password=None): def new_filter(request):
''' return filter(request, old_filter)
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
res._filter = new_filter
host: Address of the proxy. Ex: '192.168.0.100' return res
port: Port of the proxy. Ex: 6000
user: User for proxy authorization. def set_proxy(self, host, port, user=None, password=None):
password: Password for proxy authorization. '''
''' Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
self._httpclient.set_proxy(host, port, user, password)
host: Address of the proxy. Ex: '192.168.0.100'
#--Helper functions -------------------------------------------------- port: Port of the proxy. Ex: 6000
def _perform_request(self, request): user: User for proxy authorization.
try: password: Password for proxy authorization.
resp = self._filter(request) '''
except HTTPError as ex: self._httpclient.set_proxy(host, port, user, password)
return _management_error_handler(ex)
def perform_get(self, path, x_ms_version=None):
return resp '''
Performs a GET request and returns the response.
def _perform_get(self, path, response_type):
request = HTTPRequest() path:
request.method = 'GET' Path to the resource.
request.host = self.host Ex: '/<subscription-id>/services/hostedservices/<service-name>'
request.path = path x_ms_version:
request.path, request.query = _update_request_uri_query(request) If specified, this is used for the x-ms-version header.
request.headers = _update_management_header(request) Otherwise, self.x_ms_version is used.
response = self._perform_request(request) '''
request = HTTPRequest()
if response_type is not None: request.method = 'GET'
return _parse_response(response, response_type) request.host = self.host
request.path = path
return response request.path, request.query = _update_request_uri_query(request)
request.headers = self._update_management_header(request, x_ms_version)
def _perform_put(self, path, body, async=False): response = self._perform_request(request)
request = HTTPRequest()
request.method = 'PUT' return response
request.host = self.host
request.path = path def perform_put(self, path, body, x_ms_version=None):
request.body = _get_request_body(body) '''
request.path, request.query = _update_request_uri_query(request) Performs a PUT request and returns the response.
request.headers = _update_management_header(request)
response = self._perform_request(request) path:
Path to the resource.
if async: Ex: '/<subscription-id>/services/hostedservices/<service-name>'
return _parse_response_for_async_op(response) body:
Body for the PUT request.
return None x_ms_version:
If specified, this is used for the x-ms-version header.
def _perform_post(self, path, body, response_type=None, async=False): Otherwise, self.x_ms_version is used.
request = HTTPRequest() '''
request.method = 'POST' request = HTTPRequest()
request.host = self.host request.method = 'PUT'
request.path = path request.host = self.host
request.body = _get_request_body(body) request.path = path
request.path, request.query = _update_request_uri_query(request) request.body = _get_request_body(body)
request.headers = _update_management_header(request) request.path, request.query = _update_request_uri_query(request)
response = self._perform_request(request) request.headers = self._update_management_header(request, x_ms_version)
response = self._perform_request(request)
if response_type is not None:
return _parse_response(response, response_type) return response
if async: def perform_post(self, path, body, x_ms_version=None):
return _parse_response_for_async_op(response) '''
Performs a POST request and returns the response.
return None
path:
def _perform_delete(self, path, async=False): Path to the resource.
request = HTTPRequest() Ex: '/<subscription-id>/services/hostedservices/<service-name>'
request.method = 'DELETE' body:
request.host = self.host Body for the POST request.
request.path = path x_ms_version:
request.path, request.query = _update_request_uri_query(request) If specified, this is used for the x-ms-version header.
request.headers = _update_management_header(request) Otherwise, self.x_ms_version is used.
response = self._perform_request(request) '''
request = HTTPRequest()
if async: request.method = 'POST'
return _parse_response_for_async_op(response) request.host = self.host
request.path = path
return None request.body = _get_request_body(body)
request.path, request.query = _update_request_uri_query(request)
def _get_path(self, resource, name): request.headers = self._update_management_header(request, x_ms_version)
path = '/' + self.subscription_id + '/' + resource response = self._perform_request(request)
if name is not None:
path += '/' + _str(name) return response
return path
def perform_delete(self, path, x_ms_version=None):
'''
Performs a DELETE request and returns the response.
path:
Path to the resource.
Ex: '/<subscription-id>/services/hostedservices/<service-name>'
x_ms_version:
If specified, this is used for the x-ms-version header.
Otherwise, self.x_ms_version is used.
'''
request = HTTPRequest()
request.method = 'DELETE'
request.host = self.host
request.path = path
request.path, request.query = _update_request_uri_query(request)
request.headers = self._update_management_header(request, x_ms_version)
response = self._perform_request(request)
return response
#--Helper functions --------------------------------------------------
def _perform_request(self, request):
try:
resp = self._filter(request)
except HTTPError as ex:
return _management_error_handler(ex)
return resp
def _update_management_header(self, request, x_ms_version):
''' Add additional headers for management. '''
if request.method in ['PUT', 'POST', 'MERGE', 'DELETE']:
request.headers.append(('Content-Length', str(len(request.body))))
# append additional headers base on the service
request.headers.append(('x-ms-version', x_ms_version or self.x_ms_version))
# if it is not GET or HEAD request, must set content-type.
if not request.method in ['GET', 'HEAD']:
for name, _ in request.headers:
if 'content-type' == name.lower():
break
else:
request.headers.append(
('Content-Type',
self.content_type))
return request.headers
def _perform_get(self, path, response_type, x_ms_version=None):
response = self.perform_get(path, x_ms_version)
if response_type is not None:
return _parse_response(response, response_type)
return response
def _perform_put(self, path, body, async=False, x_ms_version=None):
response = self.perform_put(path, body, x_ms_version)
if async:
return parse_response_for_async_op(response)
return None
def _perform_post(self, path, body, response_type=None, async=False,
x_ms_version=None):
response = self.perform_post(path, body, x_ms_version)
if response_type is not None:
return _parse_response(response, response_type)
if async:
return parse_response_for_async_op(response)
return None
def _perform_delete(self, path, async=False, x_ms_version=None):
response = self.perform_delete(path, x_ms_version)
if async:
return parse_response_for_async_op(response)
return None
def _get_path(self, resource, name):
path = '/' + self.subscription_id + '/' + resource
if name is not None:
path += '/' + _str(name)
return path

View File

@@ -0,0 +1,390 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#--------------------------------------------------------------------------
from azure import (
MANAGEMENT_HOST,
_parse_service_resources_response,
_validate_not_none,
)
from azure.servicemanagement import (
EventLog,
ServerQuota,
Servers,
ServiceObjective,
Database,
FirewallRule,
_SqlManagementXmlSerializer,
)
from azure.servicemanagement.servicemanagementclient import (
_ServiceManagementClient,
)
class SqlDatabaseManagementService(_ServiceManagementClient):
''' Note that this class is a preliminary work on SQL Database
management. Since it lack a lot a features, final version
can be slightly different from the current one.
'''
def __init__(self, subscription_id=None, cert_file=None,
host=MANAGEMENT_HOST, request_session=None):
'''
Initializes the sql database management service.
subscription_id: Subscription to manage.
cert_file:
Path to .pem certificate file (httplib), or location of the
certificate in your Personal certificate store (winhttp) in the
CURRENT_USER\my\CertificateName format.
If a request_session is specified, then this is unused.
host: Live ServiceClient URL. Defaults to Azure public cloud.
request_session:
Session object to use for http requests. If this is specified, it
replaces the default use of httplib or winhttp. Also, the cert_file
parameter is unused when a session is passed in.
The session object handles authentication, and as such can support
multiple types of authentication: .pem certificate, oauth.
For example, you can pass in a Session instance from the requests
library. To use .pem certificate authentication with requests
library, set the path to the .pem file on the session.cert
attribute.
'''
super(SqlDatabaseManagementService, self).__init__(
subscription_id, cert_file, host, request_session)
self.content_type = 'application/xml'
#--Operations for sql servers ----------------------------------------
def create_server(self, admin_login, admin_password, location):
'''
Create a new Azure SQL Database server.
admin_login: The administrator login name for the new server.
admin_password: The administrator login password for the new server.
location: The region to deploy the new server.
'''
_validate_not_none('admin_login', admin_login)
_validate_not_none('admin_password', admin_password)
_validate_not_none('location', location)
response = self.perform_post(
self._get_servers_path(),
_SqlManagementXmlSerializer.create_server_to_xml(
admin_login,
admin_password,
location
)
)
return _SqlManagementXmlSerializer.xml_to_create_server_response(
response.body)
def set_server_admin_password(self, server_name, admin_password):
'''
Reset the administrator password for a server.
server_name: Name of the server to change the password.
admin_password: The new administrator password for the server.
'''
_validate_not_none('server_name', server_name)
_validate_not_none('admin_password', admin_password)
return self._perform_post(
self._get_servers_path(server_name) + '?op=ResetPassword',
_SqlManagementXmlSerializer.set_server_admin_password_to_xml(
admin_password
)
)
def delete_server(self, server_name):
'''
Deletes an Azure SQL Database server (including all its databases).
server_name: Name of the server you want to delete.
'''
_validate_not_none('server_name', server_name)
return self._perform_delete(
self._get_servers_path(server_name))
def list_servers(self):
'''
List the SQL servers defined on the account.
'''
return self._perform_get(self._get_servers_path(),
Servers)
def list_quotas(self, server_name):
'''
Gets quotas for an Azure SQL Database Server.
server_name: Name of the server.
'''
_validate_not_none('server_name', server_name)
response = self._perform_get(self._get_quotas_path(server_name),
None)
return _parse_service_resources_response(response, ServerQuota)
def get_server_event_logs(self, server_name, start_date,
interval_size_in_minutes, event_types=''):
'''
Gets the event logs for an Azure SQL Database Server.
server_name: Name of the server to retrieve the event logs from.
start_date:
The starting date and time of the events to retrieve in UTC format,
for example '2011-09-28 16:05:00'.
interval_size_in_minutes:
Size of the event logs to retrieve (in minutes).
Valid values are: 5, 60, or 1440.
event_types:
The event type of the log entries you want to retrieve.
Valid values are:
- connection_successful
- connection_failed
- connection_terminated
- deadlock
- throttling
- throttling_long_transaction
To return all event types pass in an empty string.
'''
_validate_not_none('server_name', server_name)
_validate_not_none('start_date', start_date)
_validate_not_none('interval_size_in_minutes', interval_size_in_minutes)
_validate_not_none('event_types', event_types)
path = self._get_server_event_logs_path(server_name) + \
'?startDate={0}&intervalSizeInMinutes={1}&eventTypes={2}'.format(
start_date, interval_size_in_minutes, event_types)
response = self._perform_get(path, None)
return _parse_service_resources_response(response, EventLog)
#--Operations for firewall rules ------------------------------------------
def create_firewall_rule(self, server_name, name, start_ip_address,
end_ip_address):
'''
Creates an Azure SQL Database server firewall rule.
server_name: Name of the server to set the firewall rule on.
name: The name of the new firewall rule.
start_ip_address:
The lowest IP address in the range of the server-level firewall
setting. IP addresses equal to or greater than this can attempt to
connect to the server. The lowest possible IP address is 0.0.0.0.
end_ip_address:
The highest IP address in the range of the server-level firewall
setting. IP addresses equal to or less than this can attempt to
connect to the server. The highest possible IP address is
255.255.255.255.
'''
_validate_not_none('server_name', server_name)
_validate_not_none('name', name)
_validate_not_none('start_ip_address', start_ip_address)
_validate_not_none('end_ip_address', end_ip_address)
return self._perform_post(
self._get_firewall_rules_path(server_name),
_SqlManagementXmlSerializer.create_firewall_rule_to_xml(
name, start_ip_address, end_ip_address
)
)
def update_firewall_rule(self, server_name, name, start_ip_address,
end_ip_address):
'''
Update a firewall rule for an Azure SQL Database server.
server_name: Name of the server to set the firewall rule on.
name: The name of the firewall rule to update.
start_ip_address:
The lowest IP address in the range of the server-level firewall
setting. IP addresses equal to or greater than this can attempt to
connect to the server. The lowest possible IP address is 0.0.0.0.
end_ip_address:
The highest IP address in the range of the server-level firewall
setting. IP addresses equal to or less than this can attempt to
connect to the server. The highest possible IP address is
255.255.255.255.
'''
_validate_not_none('server_name', server_name)
_validate_not_none('name', name)
_validate_not_none('start_ip_address', start_ip_address)
_validate_not_none('end_ip_address', end_ip_address)
return self._perform_put(
self._get_firewall_rules_path(server_name, name),
_SqlManagementXmlSerializer.update_firewall_rule_to_xml(
name, start_ip_address, end_ip_address
)
)
def delete_firewall_rule(self, server_name, name):
'''
Deletes an Azure SQL Database server firewall rule.
server_name:
Name of the server with the firewall rule you want to delete.
name:
Name of the firewall rule you want to delete.
'''
_validate_not_none('server_name', server_name)
_validate_not_none('name', name)
return self._perform_delete(
self._get_firewall_rules_path(server_name, name))
def list_firewall_rules(self, server_name):
'''
Retrieves the set of firewall rules for an Azure SQL Database Server.
server_name: Name of the server.
'''
_validate_not_none('server_name', server_name)
response = self._perform_get(self._get_firewall_rules_path(server_name),
None)
return _parse_service_resources_response(response, FirewallRule)
def list_service_level_objectives(self, server_name):
'''
Gets the service level objectives for an Azure SQL Database server.
server_name: Name of the server.
'''
_validate_not_none('server_name', server_name)
response = self._perform_get(
self._get_service_objectives_path(server_name), None)
return _parse_service_resources_response(response, ServiceObjective)
#--Operations for sql databases ----------------------------------------
def create_database(self, server_name, name, service_objective_id,
edition=None, collation_name=None,
max_size_bytes=None):
'''
Creates a new Azure SQL Database.
server_name: Name of the server to contain the new database.
name:
Required. The name for the new database. See Naming Requirements
in Azure SQL Database General Guidelines and Limitations and
Database Identifiers for more information.
service_objective_id:
Required. The GUID corresponding to the performance level for
Edition. See List Service Level Objectives for current values.
edition:
Optional. The Service Tier (Edition) for the new database. If
omitted, the default is Web. Valid values are Web, Business,
Basic, Standard, and Premium. See Azure SQL Database Service Tiers
(Editions) and Web and Business Edition Sunset FAQ for more
information.
collation_name:
Optional. The database collation. This can be any collation
supported by SQL. If omitted, the default collation is used. See
SQL Server Collation Support in Azure SQL Database General
Guidelines and Limitations for more information.
max_size_bytes:
Optional. Sets the maximum size, in bytes, for the database. This
value must be within the range of allowed values for Edition. If
omitted, the default value for the edition is used. See Azure SQL
Database Service Tiers (Editions) for current maximum databases
sizes. Convert MB or GB values to bytes.
1 MB = 1048576 bytes. 1 GB = 1073741824 bytes.
'''
_validate_not_none('server_name', server_name)
_validate_not_none('name', name)
_validate_not_none('service_objective_id', service_objective_id)
return self._perform_post(
self._get_databases_path(server_name),
_SqlManagementXmlSerializer.create_database_to_xml(
name, service_objective_id, edition, collation_name,
max_size_bytes
)
)
def update_database(self, server_name, name, new_database_name=None,
service_objective_id=None, edition=None,
max_size_bytes=None):
'''
Updates existing database details.
server_name: Name of the server to contain the new database.
name:
Required. The name for the new database. See Naming Requirements
in Azure SQL Database General Guidelines and Limitations and
Database Identifiers for more information.
new_database_name:
Optional. The new name for the new database.
service_objective_id:
Optional. The new service level to apply to the database. For more
information about service levels, see Azure SQL Database Service
Tiers and Performance Levels. Use List Service Level Objectives to
get the correct ID for the desired service objective.
edition:
Optional. The new edition for the new database.
max_size_bytes:
Optional. The new size of the database in bytes. For information on
available sizes for each edition, see Azure SQL Database Service
Tiers (Editions).
'''
_validate_not_none('server_name', server_name)
_validate_not_none('name', name)
return self._perform_put(
self._get_databases_path(server_name, name),
_SqlManagementXmlSerializer.update_database_to_xml(
new_database_name, service_objective_id, edition,
max_size_bytes
)
)
def delete_database(self, server_name, name):
'''
Deletes an Azure SQL Database.
server_name: Name of the server where the database is located.
name: Name of the database to delete.
'''
return self._perform_delete(self._get_databases_path(server_name, name))
def list_databases(self, name):
'''
List the SQL databases defined on the specified server name
'''
response = self._perform_get(self._get_list_databases_path(name),
None)
return _parse_service_resources_response(response, Database)
#--Helper functions --------------------------------------------------
def _get_servers_path(self, server_name=None):
return self._get_path('services/sqlservers/servers', server_name)
def _get_firewall_rules_path(self, server_name, name=None):
path = self._get_servers_path(server_name) + '/firewallrules'
if name:
path = path + '/' + name
return path
def _get_databases_path(self, server_name, name=None):
path = self._get_servers_path(server_name) + '/databases'
if name:
path = path + '/' + name
return path
def _get_server_event_logs_path(self, server_name):
return self._get_servers_path(server_name) + '/events'
def _get_service_objectives_path(self, server_name):
return self._get_servers_path(server_name) + '/serviceobjectives'
def _get_quotas_path(self, server_name, name=None):
path = self._get_servers_path(server_name) + '/serverquotas'
if name:
path = path + '/' + name
return path
def _get_list_databases_path(self, name):
# *contentview=generic is mandatory*
return self._get_path('services/sqlservers/servers/',
name) + '/databases?contentview=generic'

View File

@@ -0,0 +1,256 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#--------------------------------------------------------------------------
from azure import (
MANAGEMENT_HOST,
_str,
)
from azure.servicemanagement import (
WebSpaces,
WebSpace,
Sites,
Site,
MetricResponses,
MetricDefinitions,
PublishData,
_XmlSerializer,
)
from azure.servicemanagement.servicemanagementclient import (
_ServiceManagementClient,
)
class WebsiteManagementService(_ServiceManagementClient):
''' Note that this class is a preliminary work on WebSite
management. Since it lack a lot a features, final version
can be slightly different from the current one.
'''
def __init__(self, subscription_id=None, cert_file=None,
host=MANAGEMENT_HOST, request_session=None):
'''
Initializes the website management service.
subscription_id: Subscription to manage.
cert_file:
Path to .pem certificate file (httplib), or location of the
certificate in your Personal certificate store (winhttp) in the
CURRENT_USER\my\CertificateName format.
If a request_session is specified, then this is unused.
host: Live ServiceClient URL. Defaults to Azure public cloud.
request_session:
Session object to use for http requests. If this is specified, it
replaces the default use of httplib or winhttp. Also, the cert_file
parameter is unused when a session is passed in.
The session object handles authentication, and as such can support
multiple types of authentication: .pem certificate, oauth.
For example, you can pass in a Session instance from the requests
library. To use .pem certificate authentication with requests
library, set the path to the .pem file on the session.cert
attribute.
'''
super(WebsiteManagementService, self).__init__(
subscription_id, cert_file, host, request_session)
#--Operations for web sites ----------------------------------------
def list_webspaces(self):
'''
List the webspaces defined on the account.
'''
return self._perform_get(self._get_list_webspaces_path(),
WebSpaces)
def get_webspace(self, webspace_name):
'''
Get details of a specific webspace.
webspace_name: The name of the webspace.
'''
return self._perform_get(self._get_webspace_details_path(webspace_name),
WebSpace)
def list_sites(self, webspace_name):
'''
List the web sites defined on this webspace.
webspace_name: The name of the webspace.
'''
return self._perform_get(self._get_sites_path(webspace_name),
Sites)
def get_site(self, webspace_name, website_name):
'''
List the web sites defined on this webspace.
webspace_name: The name of the webspace.
website_name: The name of the website.
'''
return self._perform_get(self._get_sites_details_path(webspace_name,
website_name),
Site)
def create_site(self, webspace_name, website_name, geo_region, host_names,
plan='VirtualDedicatedPlan', compute_mode='Shared',
server_farm=None, site_mode=None):
'''
Create a website.
webspace_name: The name of the webspace.
website_name: The name of the website.
geo_region:
The geographical region of the webspace that will be created.
host_names:
An array of fully qualified domain names for website. Only one
hostname can be specified in the azurewebsites.net domain.
The hostname should match the name of the website. Custom domains
can only be specified for Shared or Standard websites.
plan:
This value must be 'VirtualDedicatedPlan'.
compute_mode:
This value should be 'Shared' for the Free or Paid Shared
offerings, or 'Dedicated' for the Standard offering. The default
value is 'Shared'. If you set it to 'Dedicated', you must specify
a value for the server_farm parameter.
server_farm:
The name of the Server Farm associated with this website. This is
a required value for Standard mode.
site_mode:
Can be None, 'Limited' or 'Basic'. This value is 'Limited' for the
Free offering, and 'Basic' for the Paid Shared offering. Standard
mode does not use the site_mode parameter; it uses the compute_mode
parameter.
'''
xml = _XmlSerializer.create_website_to_xml(webspace_name, website_name, geo_region, plan, host_names, compute_mode, server_farm, site_mode)
return self._perform_post(
self._get_sites_path(webspace_name),
xml,
Site)
def delete_site(self, webspace_name, website_name,
delete_empty_server_farm=False, delete_metrics=False):
'''
Delete a website.
webspace_name: The name of the webspace.
website_name: The name of the website.
delete_empty_server_farm:
If the site being deleted is the last web site in a server farm,
you can delete the server farm by setting this to True.
delete_metrics:
To also delete the metrics for the site that you are deleting, you
can set this to True.
'''
path = self._get_sites_details_path(webspace_name, website_name)
query = ''
if delete_empty_server_farm:
query += '&deleteEmptyServerFarm=true'
if delete_metrics:
query += '&deleteMetrics=true'
if query:
path = path + '?' + query.lstrip('&')
return self._perform_delete(path)
def restart_site(self, webspace_name, website_name):
'''
Restart a web site.
webspace_name: The name of the webspace.
website_name: The name of the website.
'''
return self._perform_post(
self._get_restart_path(webspace_name, website_name),
'')
def get_historical_usage_metrics(self, webspace_name, website_name,
metrics = None, start_time=None, end_time=None, time_grain=None):
'''
Get historical usage metrics.
webspace_name: The name of the webspace.
website_name: The name of the website.
metrics: Optional. List of metrics name. Otherwise, all metrics returned.
start_time: Optional. An ISO8601 date. Otherwise, current hour is used.
end_time: Optional. An ISO8601 date. Otherwise, current time is used.
time_grain: Optional. A rollup name, as P1D. OTherwise, default rollup for the metrics is used.
More information and metrics name at:
http://msdn.microsoft.com/en-us/library/azure/dn166964.aspx
'''
metrics = ('names='+','.join(metrics)) if metrics else ''
start_time = ('StartTime='+start_time) if start_time else ''
end_time = ('EndTime='+end_time) if end_time else ''
time_grain = ('TimeGrain='+time_grain) if time_grain else ''
parameters = ('&'.join(v for v in (metrics, start_time, end_time, time_grain) if v))
parameters = '?'+parameters if parameters else ''
return self._perform_get(self._get_historical_usage_metrics_path(webspace_name, website_name) + parameters,
MetricResponses)
def get_metric_definitions(self, webspace_name, website_name):
'''
Get metric definitions of metrics available of this web site.
webspace_name: The name of the webspace.
website_name: The name of the website.
'''
return self._perform_get(self._get_metric_definitions_path(webspace_name, website_name),
MetricDefinitions)
def get_publish_profile_xml(self, webspace_name, website_name):
'''
Get a site's publish profile as a string
webspace_name: The name of the webspace.
website_name: The name of the website.
'''
return self._perform_get(self._get_publishxml_path(webspace_name, website_name),
None).body.decode("utf-8")
def get_publish_profile(self, webspace_name, website_name):
'''
Get a site's publish profile as an object
webspace_name: The name of the webspace.
website_name: The name of the website.
'''
return self._perform_get(self._get_publishxml_path(webspace_name, website_name),
PublishData)
#--Helper functions --------------------------------------------------
def _get_list_webspaces_path(self):
return self._get_path('services/webspaces', None)
def _get_webspace_details_path(self, webspace_name):
return self._get_path('services/webspaces/', webspace_name)
def _get_sites_path(self, webspace_name):
return self._get_path('services/webspaces/',
webspace_name) + '/sites'
def _get_sites_details_path(self, webspace_name, website_name):
return self._get_path('services/webspaces/',
webspace_name) + '/sites/' + _str(website_name)
def _get_restart_path(self, webspace_name, website_name):
return self._get_path('services/webspaces/',
webspace_name) + '/sites/' + _str(website_name) + '/restart/'
def _get_historical_usage_metrics_path(self, webspace_name, website_name):
return self._get_path('services/webspaces/',
webspace_name) + '/sites/' + _str(website_name) + '/metrics/'
def _get_metric_definitions_path(self, webspace_name, website_name):
return self._get_path('services/webspaces/',
webspace_name) + '/sites/' + _str(website_name) + '/metricdefinitions/'
def _get_publishxml_path(self, webspace_name, website_name):
return self._get_path('services/webspaces/',
webspace_name) + '/sites/' + _str(website_name) + '/publishxml/'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +1,39 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
from azure.storage.blobservice import BlobService from azure.storage.blobservice import BlobService
from azure.storage.tableservice import TableService from azure.storage.tableservice import TableService
from azure.storage.queueservice import QueueService from azure.storage.queueservice import QueueService
class CloudStorageAccount(object): class CloudStorageAccount(object):
""" """
Provides a factory for creating the blob, queue, and table services Provides a factory for creating the blob, queue, and table services
with a common account name and account key. Users can either use the with a common account name and account key. Users can either use the
factory or can construct the appropriate service directly. factory or can construct the appropriate service directly.
""" """
def __init__(self, account_name=None, account_key=None): def __init__(self, account_name=None, account_key=None):
self.account_name = account_name self.account_name = account_name
self.account_key = account_key self.account_key = account_key
def create_blob_service(self): def create_blob_service(self):
return BlobService(self.account_name, self.account_key) return BlobService(self.account_name, self.account_key)
def create_table_service(self): def create_table_service(self):
return TableService(self.account_name, self.account_key) return TableService(self.account_name, self.account_key)
def create_queue_service(self): def create_queue_service(self):
return QueueService(self.account_name, self.account_key) return QueueService(self.account_name, self.account_key)

View File

@@ -1,458 +1,458 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
from azure import ( from azure import (
WindowsAzureConflictError, WindowsAzureConflictError,
WindowsAzureError, WindowsAzureError,
DEV_QUEUE_HOST, DEV_QUEUE_HOST,
QUEUE_SERVICE_HOST_BASE, QUEUE_SERVICE_HOST_BASE,
xml_escape, xml_escape,
_convert_class_to_xml, _convert_class_to_xml,
_dont_fail_not_exist, _dont_fail_not_exist,
_dont_fail_on_exist, _dont_fail_on_exist,
_get_request_body, _get_request_body,
_int_or_none, _int_or_none,
_parse_enum_results_list, _parse_enum_results_list,
_parse_response, _parse_response,
_parse_response_for_dict_filter, _parse_response_for_dict_filter,
_parse_response_for_dict_prefix, _parse_response_for_dict_prefix,
_str, _str,
_str_or_none, _str_or_none,
_update_request_uri_query_local_storage, _update_request_uri_query_local_storage,
_validate_not_none, _validate_not_none,
_ERROR_CONFLICT, _ERROR_CONFLICT,
) )
from azure.http import ( from azure.http import (
HTTPRequest, HTTPRequest,
HTTP_RESPONSE_NO_CONTENT, HTTP_RESPONSE_NO_CONTENT,
) )
from azure.storage import ( from azure.storage import (
Queue, Queue,
QueueEnumResults, QueueEnumResults,
QueueMessagesList, QueueMessagesList,
StorageServiceProperties, StorageServiceProperties,
_update_storage_queue_header, _update_storage_queue_header,
) )
from azure.storage.storageclient import _StorageClient from azure.storage.storageclient import _StorageClient
class QueueService(_StorageClient): class QueueService(_StorageClient):
''' '''
This is the main class managing queue resources. This is the main class managing queue resources.
''' '''
def __init__(self, account_name=None, account_key=None, protocol='https', def __init__(self, account_name=None, account_key=None, protocol='https',
host_base=QUEUE_SERVICE_HOST_BASE, dev_host=DEV_QUEUE_HOST): host_base=QUEUE_SERVICE_HOST_BASE, dev_host=DEV_QUEUE_HOST):
''' '''
account_name: your storage account name, required for all operations. account_name: your storage account name, required for all operations.
account_key: your storage account key, required for all operations. account_key: your storage account key, required for all operations.
protocol: Optional. Protocol. Defaults to http. protocol: Optional. Protocol. Defaults to http.
host_base: host_base:
Optional. Live host base url. Defaults to Azure url. Override this Optional. Live host base url. Defaults to Azure url. Override this
for on-premise. for on-premise.
dev_host: Optional. Dev host url. Defaults to localhost. dev_host: Optional. Dev host url. Defaults to localhost.
''' '''
super(QueueService, self).__init__( super(QueueService, self).__init__(
account_name, account_key, protocol, host_base, dev_host) account_name, account_key, protocol, host_base, dev_host)
def get_queue_service_properties(self, timeout=None): def get_queue_service_properties(self, timeout=None):
''' '''
Gets the properties of a storage account's Queue Service, including Gets the properties of a storage account's Queue Service, including
Windows Azure Storage Analytics. Windows Azure Storage Analytics.
timeout: Optional. The timeout parameter is expressed in seconds. timeout: Optional. The timeout parameter is expressed in seconds.
''' '''
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/?restype=service&comp=properties' request.path = '/?restype=service&comp=properties'
request.query = [('timeout', _int_or_none(timeout))] request.query = [('timeout', _int_or_none(timeout))]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response(response, StorageServiceProperties) return _parse_response(response, StorageServiceProperties)
def list_queues(self, prefix=None, marker=None, maxresults=None, def list_queues(self, prefix=None, marker=None, maxresults=None,
include=None): include=None):
''' '''
Lists all of the queues in a given storage account. Lists all of the queues in a given storage account.
prefix: prefix:
Filters the results to return only queues with names that begin Filters the results to return only queues with names that begin
with the specified prefix. with the specified prefix.
marker: marker:
A string value that identifies the portion of the list to be A string value that identifies the portion of the list to be
returned with the next list operation. The operation returns a returned with the next list operation. The operation returns a
NextMarker element within the response body if the list returned NextMarker element within the response body if the list returned
was not complete. This value may then be used as a query parameter was not complete. This value may then be used as a query parameter
in a subsequent call to request the next portion of the list of in a subsequent call to request the next portion of the list of
queues. The marker value is opaque to the client. queues. The marker value is opaque to the client.
maxresults: maxresults:
Specifies the maximum number of queues to return. If maxresults is Specifies the maximum number of queues to return. If maxresults is
not specified, the server will return up to 5,000 items. not specified, the server will return up to 5,000 items.
include: include:
Optional. Include this parameter to specify that the container's Optional. Include this parameter to specify that the container's
metadata be returned as part of the response body. metadata be returned as part of the response body.
''' '''
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/?comp=list' request.path = '/?comp=list'
request.query = [ request.query = [
('prefix', _str_or_none(prefix)), ('prefix', _str_or_none(prefix)),
('marker', _str_or_none(marker)), ('marker', _str_or_none(marker)),
('maxresults', _int_or_none(maxresults)), ('maxresults', _int_or_none(maxresults)),
('include', _str_or_none(include)) ('include', _str_or_none(include))
] ]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_enum_results_list( return _parse_enum_results_list(
response, QueueEnumResults, "Queues", Queue) response, QueueEnumResults, "Queues", Queue)
def create_queue(self, queue_name, x_ms_meta_name_values=None, def create_queue(self, queue_name, x_ms_meta_name_values=None,
fail_on_exist=False): fail_on_exist=False):
''' '''
Creates a queue under the given account. Creates a queue under the given account.
queue_name: name of the queue. queue_name: name of the queue.
x_ms_meta_name_values: x_ms_meta_name_values:
Optional. A dict containing name-value pairs to associate with the Optional. A dict containing name-value pairs to associate with the
queue as metadata. queue as metadata.
fail_on_exist: Specify whether throw exception when queue exists. fail_on_exist: Specify whether throw exception when queue exists.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '' request.path = '/' + _str(queue_name) + ''
request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)] request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
if not fail_on_exist: if not fail_on_exist:
try: try:
response = self._perform_request(request) response = self._perform_request(request)
if response.status == HTTP_RESPONSE_NO_CONTENT: if response.status == HTTP_RESPONSE_NO_CONTENT:
return False return False
return True return True
except WindowsAzureError as ex: except WindowsAzureError as ex:
_dont_fail_on_exist(ex) _dont_fail_on_exist(ex)
return False return False
else: else:
response = self._perform_request(request) response = self._perform_request(request)
if response.status == HTTP_RESPONSE_NO_CONTENT: if response.status == HTTP_RESPONSE_NO_CONTENT:
raise WindowsAzureConflictError( raise WindowsAzureConflictError(
_ERROR_CONFLICT.format(response.message)) _ERROR_CONFLICT.format(response.message))
return True return True
def delete_queue(self, queue_name, fail_not_exist=False): def delete_queue(self, queue_name, fail_not_exist=False):
''' '''
Permanently deletes the specified queue. Permanently deletes the specified queue.
queue_name: Name of the queue. queue_name: Name of the queue.
fail_not_exist: fail_not_exist:
Specify whether throw exception when queue doesn't exist. Specify whether throw exception when queue doesn't exist.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'DELETE' request.method = 'DELETE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '' request.path = '/' + _str(queue_name) + ''
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
if not fail_not_exist: if not fail_not_exist:
try: try:
self._perform_request(request) self._perform_request(request)
return True return True
except WindowsAzureError as ex: except WindowsAzureError as ex:
_dont_fail_not_exist(ex) _dont_fail_not_exist(ex)
return False return False
else: else:
self._perform_request(request) self._perform_request(request)
return True return True
def get_queue_metadata(self, queue_name): def get_queue_metadata(self, queue_name):
''' '''
Retrieves user-defined metadata and queue properties on the specified Retrieves user-defined metadata and queue properties on the specified
queue. Metadata is associated with the queue as name-values pairs. queue. Metadata is associated with the queue as name-values pairs.
queue_name: Name of the queue. queue_name: Name of the queue.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '?comp=metadata' request.path = '/' + _str(queue_name) + '?comp=metadata'
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict_prefix( return _parse_response_for_dict_prefix(
response, response,
prefixes=['x-ms-meta', 'x-ms-approximate-messages-count']) prefixes=['x-ms-meta', 'x-ms-approximate-messages-count'])
def set_queue_metadata(self, queue_name, x_ms_meta_name_values=None): def set_queue_metadata(self, queue_name, x_ms_meta_name_values=None):
''' '''
Sets user-defined metadata on the specified queue. Metadata is Sets user-defined metadata on the specified queue. Metadata is
associated with the queue as name-value pairs. associated with the queue as name-value pairs.
queue_name: Name of the queue. queue_name: Name of the queue.
x_ms_meta_name_values: x_ms_meta_name_values:
Optional. A dict containing name-value pairs to associate with the Optional. A dict containing name-value pairs to associate with the
queue as metadata. queue as metadata.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '?comp=metadata' request.path = '/' + _str(queue_name) + '?comp=metadata'
request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)] request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
self._perform_request(request) self._perform_request(request)
def put_message(self, queue_name, message_text, visibilitytimeout=None, def put_message(self, queue_name, message_text, visibilitytimeout=None,
messagettl=None): messagettl=None):
''' '''
Adds a new message to the back of the message queue. A visibility Adds a new message to the back of the message queue. A visibility
timeout can also be specified to make the message invisible until the timeout can also be specified to make the message invisible until the
visibility timeout expires. A message must be in a format that can be visibility timeout expires. A message must be in a format that can be
included in an XML request with UTF-8 encoding. The encoded message can included in an XML request with UTF-8 encoding. The encoded message can
be up to 64KB in size for versions 2011-08-18 and newer, or 8KB in size be up to 64KB in size for versions 2011-08-18 and newer, or 8KB in size
for previous versions. for previous versions.
queue_name: Name of the queue. queue_name: Name of the queue.
message_text: Message content. message_text: Message content.
visibilitytimeout: visibilitytimeout:
Optional. If not specified, the default value is 0. Specifies the Optional. If not specified, the default value is 0. Specifies the
new visibility timeout value, in seconds, relative to server time. new visibility timeout value, in seconds, relative to server time.
The new value must be larger than or equal to 0, and cannot be The new value must be larger than or equal to 0, and cannot be
larger than 7 days. The visibility timeout of a message cannot be larger than 7 days. The visibility timeout of a message cannot be
set to a value later than the expiry time. visibilitytimeout set to a value later than the expiry time. visibilitytimeout
should be set to a value smaller than the time-to-live value. should be set to a value smaller than the time-to-live value.
messagettl: messagettl:
Optional. Specifies the time-to-live interval for the message, in Optional. Specifies the time-to-live interval for the message, in
seconds. The maximum time-to-live allowed is 7 days. If this seconds. The maximum time-to-live allowed is 7 days. If this
parameter is omitted, the default time-to-live is 7 days. parameter is omitted, the default time-to-live is 7 days.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
_validate_not_none('message_text', message_text) _validate_not_none('message_text', message_text)
request = HTTPRequest() request = HTTPRequest()
request.method = 'POST' request.method = 'POST'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '/messages' request.path = '/' + _str(queue_name) + '/messages'
request.query = [ request.query = [
('visibilitytimeout', _str_or_none(visibilitytimeout)), ('visibilitytimeout', _str_or_none(visibilitytimeout)),
('messagettl', _str_or_none(messagettl)) ('messagettl', _str_or_none(messagettl))
] ]
request.body = _get_request_body( request.body = _get_request_body(
'<?xml version="1.0" encoding="utf-8"?> \ '<?xml version="1.0" encoding="utf-8"?> \
<QueueMessage> \ <QueueMessage> \
<MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \ <MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \
</QueueMessage>') </QueueMessage>')
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
self._perform_request(request) self._perform_request(request)
def get_messages(self, queue_name, numofmessages=None, def get_messages(self, queue_name, numofmessages=None,
visibilitytimeout=None): visibilitytimeout=None):
''' '''
Retrieves one or more messages from the front of the queue. Retrieves one or more messages from the front of the queue.
queue_name: Name of the queue. queue_name: Name of the queue.
numofmessages: numofmessages:
Optional. A nonzero integer value that specifies the number of Optional. A nonzero integer value that specifies the number of
messages to retrieve from the queue, up to a maximum of 32. If messages to retrieve from the queue, up to a maximum of 32. If
fewer are visible, the visible messages are returned. By default, fewer are visible, the visible messages are returned. By default,
a single message is retrieved from the queue with this operation. a single message is retrieved from the queue with this operation.
visibilitytimeout: visibilitytimeout:
Specifies the new visibility timeout value, in seconds, relative Specifies the new visibility timeout value, in seconds, relative
to server time. The new value must be larger than or equal to 1 to server time. The new value must be larger than or equal to 1
second, and cannot be larger than 7 days, or larger than 2 hours second, and cannot be larger than 7 days, or larger than 2 hours
on REST protocol versions prior to version 2011-08-18. The on REST protocol versions prior to version 2011-08-18. The
visibility timeout of a message can be set to a value later than visibility timeout of a message can be set to a value later than
the expiry time. the expiry time.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '/messages' request.path = '/' + _str(queue_name) + '/messages'
request.query = [ request.query = [
('numofmessages', _str_or_none(numofmessages)), ('numofmessages', _str_or_none(numofmessages)),
('visibilitytimeout', _str_or_none(visibilitytimeout)) ('visibilitytimeout', _str_or_none(visibilitytimeout))
] ]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response(response, QueueMessagesList) return _parse_response(response, QueueMessagesList)
def peek_messages(self, queue_name, numofmessages=None): def peek_messages(self, queue_name, numofmessages=None):
''' '''
Retrieves one or more messages from the front of the queue, but does Retrieves one or more messages from the front of the queue, but does
not alter the visibility of the message. not alter the visibility of the message.
queue_name: Name of the queue. queue_name: Name of the queue.
numofmessages: numofmessages:
Optional. A nonzero integer value that specifies the number of Optional. A nonzero integer value that specifies the number of
messages to peek from the queue, up to a maximum of 32. By default, messages to peek from the queue, up to a maximum of 32. By default,
a single message is peeked from the queue with this operation. a single message is peeked from the queue with this operation.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '/messages?peekonly=true' request.path = '/' + _str(queue_name) + '/messages?peekonly=true'
request.query = [('numofmessages', _str_or_none(numofmessages))] request.query = [('numofmessages', _str_or_none(numofmessages))]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response(response, QueueMessagesList) return _parse_response(response, QueueMessagesList)
def delete_message(self, queue_name, message_id, popreceipt): def delete_message(self, queue_name, message_id, popreceipt):
''' '''
Deletes the specified message. Deletes the specified message.
queue_name: Name of the queue. queue_name: Name of the queue.
message_id: Message to delete. message_id: Message to delete.
popreceipt: popreceipt:
Required. A valid pop receipt value returned from an earlier call Required. A valid pop receipt value returned from an earlier call
to the Get Messages or Update Message operation. to the Get Messages or Update Message operation.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
_validate_not_none('message_id', message_id) _validate_not_none('message_id', message_id)
_validate_not_none('popreceipt', popreceipt) _validate_not_none('popreceipt', popreceipt)
request = HTTPRequest() request = HTTPRequest()
request.method = 'DELETE' request.method = 'DELETE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(queue_name) + '/messages/' + _str(message_id) + '' _str(queue_name) + '/messages/' + _str(message_id) + ''
request.query = [('popreceipt', _str_or_none(popreceipt))] request.query = [('popreceipt', _str_or_none(popreceipt))]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
self._perform_request(request) self._perform_request(request)
def clear_messages(self, queue_name): def clear_messages(self, queue_name):
''' '''
Deletes all messages from the specified queue. Deletes all messages from the specified queue.
queue_name: Name of the queue. queue_name: Name of the queue.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'DELETE' request.method = 'DELETE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(queue_name) + '/messages' request.path = '/' + _str(queue_name) + '/messages'
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
self._perform_request(request) self._perform_request(request)
def update_message(self, queue_name, message_id, message_text, popreceipt, def update_message(self, queue_name, message_id, message_text, popreceipt,
visibilitytimeout): visibilitytimeout):
''' '''
Updates the visibility timeout of a message. You can also use this Updates the visibility timeout of a message. You can also use this
operation to update the contents of a message. operation to update the contents of a message.
queue_name: Name of the queue. queue_name: Name of the queue.
message_id: Message to update. message_id: Message to update.
message_text: Content of message. message_text: Content of message.
popreceipt: popreceipt:
Required. A valid pop receipt value returned from an earlier call Required. A valid pop receipt value returned from an earlier call
to the Get Messages or Update Message operation. to the Get Messages or Update Message operation.
visibilitytimeout: visibilitytimeout:
Required. Specifies the new visibility timeout value, in seconds, Required. Specifies the new visibility timeout value, in seconds,
relative to server time. The new value must be larger than or equal relative to server time. The new value must be larger than or equal
to 0, and cannot be larger than 7 days. The visibility timeout of a to 0, and cannot be larger than 7 days. The visibility timeout of a
message cannot be set to a value later than the expiry time. A message cannot be set to a value later than the expiry time. A
message can be updated until it has been deleted or has expired. message can be updated until it has been deleted or has expired.
''' '''
_validate_not_none('queue_name', queue_name) _validate_not_none('queue_name', queue_name)
_validate_not_none('message_id', message_id) _validate_not_none('message_id', message_id)
_validate_not_none('message_text', message_text) _validate_not_none('message_text', message_text)
_validate_not_none('popreceipt', popreceipt) _validate_not_none('popreceipt', popreceipt)
_validate_not_none('visibilitytimeout', visibilitytimeout) _validate_not_none('visibilitytimeout', visibilitytimeout)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(queue_name) + '/messages/' + _str(message_id) + '' _str(queue_name) + '/messages/' + _str(message_id) + ''
request.query = [ request.query = [
('popreceipt', _str_or_none(popreceipt)), ('popreceipt', _str_or_none(popreceipt)),
('visibilitytimeout', _str_or_none(visibilitytimeout)) ('visibilitytimeout', _str_or_none(visibilitytimeout))
] ]
request.body = _get_request_body( request.body = _get_request_body(
'<?xml version="1.0" encoding="utf-8"?> \ '<?xml version="1.0" encoding="utf-8"?> \
<QueueMessage> \ <QueueMessage> \
<MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \ <MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \
</QueueMessage>') </QueueMessage>')
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict_filter( return _parse_response_for_dict_filter(
response, response,
filter=['x-ms-popreceipt', 'x-ms-time-next-visible']) filter=['x-ms-popreceipt', 'x-ms-time-next-visible'])
def set_queue_service_properties(self, storage_service_properties, def set_queue_service_properties(self, storage_service_properties,
timeout=None): timeout=None):
''' '''
Sets the properties of a storage account's Queue service, including Sets the properties of a storage account's Queue service, including
Windows Azure Storage Analytics. Windows Azure Storage Analytics.
storage_service_properties: StorageServiceProperties object. storage_service_properties: StorageServiceProperties object.
timeout: Optional. The timeout parameter is expressed in seconds. timeout: Optional. The timeout parameter is expressed in seconds.
''' '''
_validate_not_none('storage_service_properties', _validate_not_none('storage_service_properties',
storage_service_properties) storage_service_properties)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/?restype=service&comp=properties' request.path = '/?restype=service&comp=properties'
request.query = [('timeout', _int_or_none(timeout))] request.query = [('timeout', _int_or_none(timeout))]
request.body = _get_request_body( request.body = _get_request_body(
_convert_class_to_xml(storage_service_properties)) _convert_class_to_xml(storage_service_properties))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_queue_header( request.headers = _update_storage_queue_header(
request, self.account_name, self.account_key) request, self.account_name, self.account_key)
self._perform_request(request) self._perform_request(request)

View File

@@ -1,230 +1,231 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
from azure import url_quote from azure import _sign_string, url_quote
from azure.storage import _sign_string, X_MS_VERSION from azure.storage import X_MS_VERSION
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Constants for the share access signature # Constants for the share access signature
SIGNED_START = 'st' SIGNED_VERSION = 'sv'
SIGNED_EXPIRY = 'se' SIGNED_START = 'st'
SIGNED_RESOURCE = 'sr' SIGNED_EXPIRY = 'se'
SIGNED_PERMISSION = 'sp' SIGNED_RESOURCE = 'sr'
SIGNED_IDENTIFIER = 'si' SIGNED_PERMISSION = 'sp'
SIGNED_SIGNATURE = 'sig' SIGNED_IDENTIFIER = 'si'
SIGNED_VERSION = 'sv' SIGNED_SIGNATURE = 'sig'
RESOURCE_BLOB = 'b' SIGNED_VERSION = 'sv'
RESOURCE_CONTAINER = 'c' RESOURCE_BLOB = 'b'
SIGNED_RESOURCE_TYPE = 'resource' RESOURCE_CONTAINER = 'c'
SHARED_ACCESS_PERMISSION = 'permission' SIGNED_RESOURCE_TYPE = 'resource'
SHARED_ACCESS_PERMISSION = 'permission'
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
class WebResource(object):
class WebResource(object):
'''
Class that stands for the resource to get the share access signature '''
Class that stands for the resource to get the share access signature
path: the resource path.
properties: dict of name and values. Contains 2 item: resource type and path: the resource path.
permission properties: dict of name and values. Contains 2 item: resource type and
request_url: the url of the webresource include all the queries. permission
''' request_url: the url of the webresource include all the queries.
'''
def __init__(self, path=None, request_url=None, properties=None):
self.path = path def __init__(self, path=None, request_url=None, properties=None):
self.properties = properties or {} self.path = path
self.request_url = request_url self.properties = properties or {}
self.request_url = request_url
class Permission(object):
class Permission(object):
'''
Permission class. Contains the path and query_string for the path. '''
Permission class. Contains the path and query_string for the path.
path: the resource path
query_string: dict of name, values. Contains SIGNED_START, SIGNED_EXPIRY path: the resource path
SIGNED_RESOURCE, SIGNED_PERMISSION, SIGNED_IDENTIFIER, query_string: dict of name, values. Contains SIGNED_START, SIGNED_EXPIRY
SIGNED_SIGNATURE name values. SIGNED_RESOURCE, SIGNED_PERMISSION, SIGNED_IDENTIFIER,
''' SIGNED_SIGNATURE name values.
'''
def __init__(self, path=None, query_string=None):
self.path = path def __init__(self, path=None, query_string=None):
self.query_string = query_string self.path = path
self.query_string = query_string
class SharedAccessPolicy(object):
class SharedAccessPolicy(object):
''' SharedAccessPolicy class. '''
''' SharedAccessPolicy class. '''
def __init__(self, access_policy, signed_identifier=None):
self.id = signed_identifier def __init__(self, access_policy, signed_identifier=None):
self.access_policy = access_policy self.id = signed_identifier
self.access_policy = access_policy
class SharedAccessSignature(object):
class SharedAccessSignature(object):
'''
The main class used to do the signing and generating the signature. '''
The main class used to do the signing and generating the signature.
account_name:
the storage account name used to generate shared access signature account_name:
account_key: the access key to genenerate share access signature the storage account name used to generate shared access signature
permission_set: the permission cache used to signed the request url. account_key: the access key to genenerate share access signature
''' permission_set: the permission cache used to signed the request url.
'''
def __init__(self, account_name, account_key, permission_set=None):
self.account_name = account_name def __init__(self, account_name, account_key, permission_set=None):
self.account_key = account_key self.account_name = account_name
self.permission_set = permission_set self.account_key = account_key
self.permission_set = permission_set
def generate_signed_query_string(self, path, resource_type,
shared_access_policy, def generate_signed_query_string(self, path, resource_type,
version=X_MS_VERSION): shared_access_policy,
''' version=X_MS_VERSION):
Generates the query string for path, resource type and shared access '''
policy. Generates the query string for path, resource type and shared access
policy.
path: the resource
resource_type: could be blob or container path: the resource
shared_access_policy: shared access policy resource_type: could be blob or container
version: shared_access_policy: shared access policy
x-ms-version for storage service, or None to get a signed query version:
string compatible with pre 2012-02-12 clients, where the version x-ms-version for storage service, or None to get a signed query
is not included in the query string. string compatible with pre 2012-02-12 clients, where the version
''' is not included in the query string.
'''
query_string = {}
if shared_access_policy.access_policy.start: query_string = {}
query_string[ if shared_access_policy.access_policy.start:
SIGNED_START] = shared_access_policy.access_policy.start query_string[
SIGNED_START] = shared_access_policy.access_policy.start
if version:
query_string[SIGNED_VERSION] = version if version:
query_string[SIGNED_EXPIRY] = shared_access_policy.access_policy.expiry query_string[SIGNED_VERSION] = version
query_string[SIGNED_RESOURCE] = resource_type query_string[SIGNED_EXPIRY] = shared_access_policy.access_policy.expiry
query_string[ query_string[SIGNED_RESOURCE] = resource_type
SIGNED_PERMISSION] = shared_access_policy.access_policy.permission query_string[
SIGNED_PERMISSION] = shared_access_policy.access_policy.permission
if shared_access_policy.id:
query_string[SIGNED_IDENTIFIER] = shared_access_policy.id if shared_access_policy.id:
query_string[SIGNED_IDENTIFIER] = shared_access_policy.id
query_string[SIGNED_SIGNATURE] = self._generate_signature(
path, shared_access_policy, version) query_string[SIGNED_SIGNATURE] = self._generate_signature(
return query_string path, shared_access_policy, version)
return query_string
def sign_request(self, web_resource):
''' sign request to generate request_url with sharedaccesssignature def sign_request(self, web_resource):
info for web_resource.''' ''' sign request to generate request_url with sharedaccesssignature
info for web_resource.'''
if self.permission_set:
for shared_access_signature in self.permission_set: if self.permission_set:
if self._permission_matches_request( for shared_access_signature in self.permission_set:
shared_access_signature, web_resource, if self._permission_matches_request(
web_resource.properties[ shared_access_signature, web_resource,
SIGNED_RESOURCE_TYPE], web_resource.properties[
web_resource.properties[SHARED_ACCESS_PERMISSION]): SIGNED_RESOURCE_TYPE],
if web_resource.request_url.find('?') == -1: web_resource.properties[SHARED_ACCESS_PERMISSION]):
web_resource.request_url += '?' if web_resource.request_url.find('?') == -1:
else: web_resource.request_url += '?'
web_resource.request_url += '&' else:
web_resource.request_url += '&'
web_resource.request_url += self._convert_query_string(
shared_access_signature.query_string) web_resource.request_url += self._convert_query_string(
break shared_access_signature.query_string)
return web_resource break
return web_resource
def _convert_query_string(self, query_string):
''' Converts query string to str. The order of name, values is very def _convert_query_string(self, query_string):
important and can't be wrong.''' ''' Converts query string to str. The order of name, values is very
important and can't be wrong.'''
convert_str = ''
if SIGNED_START in query_string: convert_str = ''
convert_str += SIGNED_START + '=' + \ if SIGNED_START in query_string:
url_quote(query_string[SIGNED_START]) + '&' convert_str += SIGNED_START + '=' + \
convert_str += SIGNED_EXPIRY + '=' + \ url_quote(query_string[SIGNED_START]) + '&'
url_quote(query_string[SIGNED_EXPIRY]) + '&' convert_str += SIGNED_EXPIRY + '=' + \
convert_str += SIGNED_PERMISSION + '=' + \ url_quote(query_string[SIGNED_EXPIRY]) + '&'
query_string[SIGNED_PERMISSION] + '&' convert_str += SIGNED_PERMISSION + '=' + \
convert_str += SIGNED_RESOURCE + '=' + \ query_string[SIGNED_PERMISSION] + '&'
query_string[SIGNED_RESOURCE] + '&' convert_str += SIGNED_RESOURCE + '=' + \
query_string[SIGNED_RESOURCE] + '&'
if SIGNED_IDENTIFIER in query_string:
convert_str += SIGNED_IDENTIFIER + '=' + \ if SIGNED_IDENTIFIER in query_string:
query_string[SIGNED_IDENTIFIER] + '&' convert_str += SIGNED_IDENTIFIER + '=' + \
if SIGNED_VERSION in query_string: query_string[SIGNED_IDENTIFIER] + '&'
convert_str += SIGNED_VERSION + '=' + \ if SIGNED_VERSION in query_string:
query_string[SIGNED_VERSION] + '&' convert_str += SIGNED_VERSION + '=' + \
convert_str += SIGNED_SIGNATURE + '=' + \ query_string[SIGNED_VERSION] + '&'
url_quote(query_string[SIGNED_SIGNATURE]) + '&' convert_str += SIGNED_SIGNATURE + '=' + \
return convert_str url_quote(query_string[SIGNED_SIGNATURE]) + '&'
return convert_str
def _generate_signature(self, path, shared_access_policy, version):
''' Generates signature for a given path and shared access policy. ''' def _generate_signature(self, path, shared_access_policy, version):
''' Generates signature for a given path and shared access policy. '''
def get_value_to_append(value, no_new_line=False):
return_value = '' def get_value_to_append(value, no_new_line=False):
if value: return_value = ''
return_value = value if value:
if not no_new_line: return_value = value
return_value += '\n' if not no_new_line:
return return_value return_value += '\n'
return return_value
if path[0] != '/':
path = '/' + path if path[0] != '/':
path = '/' + path
canonicalized_resource = '/' + self.account_name + path
canonicalized_resource = '/' + self.account_name + path
# Form the string to sign from shared_access_policy and canonicalized
# resource. The order of values is important. # Form the string to sign from shared_access_policy and canonicalized
string_to_sign = \ # resource. The order of values is important.
(get_value_to_append(shared_access_policy.access_policy.permission) + string_to_sign = \
get_value_to_append(shared_access_policy.access_policy.start) + (get_value_to_append(shared_access_policy.access_policy.permission) +
get_value_to_append(shared_access_policy.access_policy.expiry) + get_value_to_append(shared_access_policy.access_policy.start) +
get_value_to_append(canonicalized_resource)) get_value_to_append(shared_access_policy.access_policy.expiry) +
get_value_to_append(canonicalized_resource))
if version:
string_to_sign += get_value_to_append(shared_access_policy.id) if version:
string_to_sign += get_value_to_append(version, True) string_to_sign += get_value_to_append(shared_access_policy.id)
else: string_to_sign += get_value_to_append(version, True)
string_to_sign += get_value_to_append(shared_access_policy.id, True) else:
string_to_sign += get_value_to_append(shared_access_policy.id, True)
return self._sign(string_to_sign)
return self._sign(string_to_sign)
def _permission_matches_request(self, shared_access_signature,
web_resource, resource_type, def _permission_matches_request(self, shared_access_signature,
required_permission): web_resource, resource_type,
''' Check whether requested permission matches given required_permission):
shared_access_signature, web_resource and resource type. ''' ''' Check whether requested permission matches given
shared_access_signature, web_resource and resource type. '''
required_resource_type = resource_type
if required_resource_type == RESOURCE_BLOB: required_resource_type = resource_type
required_resource_type += RESOURCE_CONTAINER if required_resource_type == RESOURCE_BLOB:
required_resource_type += RESOURCE_CONTAINER
for name, value in shared_access_signature.query_string.items():
if name == SIGNED_RESOURCE and \ for name, value in shared_access_signature.query_string.items():
required_resource_type.find(value) == -1: if name == SIGNED_RESOURCE and \
return False required_resource_type.find(value) == -1:
elif name == SIGNED_PERMISSION and \ return False
required_permission.find(value) == -1: elif name == SIGNED_PERMISSION and \
return False required_permission.find(value) == -1:
return False
return web_resource.path.find(shared_access_signature.path) != -1
return web_resource.path.find(shared_access_signature.path) != -1
def _sign(self, string_to_sign):
''' use HMAC-SHA256 to sign the string and convert it as base64 def _sign(self, string_to_sign):
encoded string. ''' ''' use HMAC-SHA256 to sign the string and convert it as base64
encoded string. '''
return _sign_string(self.account_key, string_to_sign)
return _sign_string(self.account_key, string_to_sign)

View File

@@ -1,152 +1,152 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
import os import os
import sys import sys
from azure import ( from azure import (
WindowsAzureError, WindowsAzureError,
DEV_ACCOUNT_NAME, DEV_ACCOUNT_NAME,
DEV_ACCOUNT_KEY, DEV_ACCOUNT_KEY,
_ERROR_STORAGE_MISSING_INFO, _ERROR_STORAGE_MISSING_INFO,
) )
from azure.http import HTTPError from azure.http import HTTPError
from azure.http.httpclient import _HTTPClient from azure.http.httpclient import _HTTPClient
from azure.storage import _storage_error_handler from azure.storage import _storage_error_handler
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
# constants for azure app setting environment variables # constants for azure app setting environment variables
AZURE_STORAGE_ACCOUNT = 'AZURE_STORAGE_ACCOUNT' AZURE_STORAGE_ACCOUNT = 'AZURE_STORAGE_ACCOUNT'
AZURE_STORAGE_ACCESS_KEY = 'AZURE_STORAGE_ACCESS_KEY' AZURE_STORAGE_ACCESS_KEY = 'AZURE_STORAGE_ACCESS_KEY'
EMULATED = 'EMULATED' EMULATED = 'EMULATED'
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
class _StorageClient(object): class _StorageClient(object):
''' '''
This is the base class for BlobManager, TableManager and QueueManager. This is the base class for BlobManager, TableManager and QueueManager.
''' '''
def __init__(self, account_name=None, account_key=None, protocol='https', def __init__(self, account_name=None, account_key=None, protocol='https',
host_base='', dev_host=''): host_base='', dev_host=''):
''' '''
account_name: your storage account name, required for all operations. account_name: your storage account name, required for all operations.
account_key: your storage account key, required for all operations. account_key: your storage account key, required for all operations.
protocol: Optional. Protocol. Defaults to http. protocol: Optional. Protocol. Defaults to http.
host_base: host_base:
Optional. Live host base url. Defaults to Azure url. Override this Optional. Live host base url. Defaults to Azure url. Override this
for on-premise. for on-premise.
dev_host: Optional. Dev host url. Defaults to localhost. dev_host: Optional. Dev host url. Defaults to localhost.
''' '''
self.account_name = account_name self.account_name = account_name
self.account_key = account_key self.account_key = account_key
self.requestid = None self.requestid = None
self.protocol = protocol self.protocol = protocol
self.host_base = host_base self.host_base = host_base
self.dev_host = dev_host self.dev_host = dev_host
# the app is not run in azure emulator or use default development # the app is not run in azure emulator or use default development
# storage account and key if app is run in emulator. # storage account and key if app is run in emulator.
self.use_local_storage = False self.use_local_storage = False
# check whether it is run in emulator. # check whether it is run in emulator.
if EMULATED in os.environ: if EMULATED in os.environ:
self.is_emulated = os.environ[EMULATED].lower() != 'false' self.is_emulated = os.environ[EMULATED].lower() != 'false'
else: else:
self.is_emulated = False self.is_emulated = False
# get account_name and account key. If they are not set when # get account_name and account key. If they are not set when
# constructing, get the account and key from environment variables if # constructing, get the account and key from environment variables if
# the app is not run in azure emulator or use default development # the app is not run in azure emulator or use default development
# storage account and key if app is run in emulator. # storage account and key if app is run in emulator.
if not self.account_name or not self.account_key: if not self.account_name or not self.account_key:
if self.is_emulated: if self.is_emulated:
self.account_name = DEV_ACCOUNT_NAME self.account_name = DEV_ACCOUNT_NAME
self.account_key = DEV_ACCOUNT_KEY self.account_key = DEV_ACCOUNT_KEY
self.protocol = 'http' self.protocol = 'http'
self.use_local_storage = True self.use_local_storage = True
else: else:
self.account_name = os.environ.get(AZURE_STORAGE_ACCOUNT) self.account_name = os.environ.get(AZURE_STORAGE_ACCOUNT)
self.account_key = os.environ.get(AZURE_STORAGE_ACCESS_KEY) self.account_key = os.environ.get(AZURE_STORAGE_ACCESS_KEY)
if not self.account_name or not self.account_key: if not self.account_name or not self.account_key:
raise WindowsAzureError(_ERROR_STORAGE_MISSING_INFO) raise WindowsAzureError(_ERROR_STORAGE_MISSING_INFO)
self._httpclient = _HTTPClient( self._httpclient = _HTTPClient(
service_instance=self, service_instance=self,
account_key=self.account_key, account_key=self.account_key,
account_name=self.account_name, account_name=self.account_name,
protocol=self.protocol) protocol=self.protocol)
self._batchclient = None self._batchclient = None
self._filter = self._perform_request_worker self._filter = self._perform_request_worker
def with_filter(self, filter): def with_filter(self, filter):
''' '''
Returns a new service which will process requests with the specified Returns a new service which will process requests with the specified
filter. Filtering operations can include logging, automatic retrying, filter. Filtering operations can include logging, automatic retrying,
etc... The filter is a lambda which receives the HTTPRequest and etc... The filter is a lambda which receives the HTTPRequest and
another lambda. The filter can perform any pre-processing on the another lambda. The filter can perform any pre-processing on the
request, pass it off to the next lambda, and then perform any request, pass it off to the next lambda, and then perform any
post-processing on the response. post-processing on the response.
''' '''
res = type(self)(self.account_name, self.account_key, self.protocol) res = type(self)(self.account_name, self.account_key, self.protocol)
old_filter = self._filter old_filter = self._filter
def new_filter(request): def new_filter(request):
return filter(request, old_filter) return filter(request, old_filter)
res._filter = new_filter res._filter = new_filter
return res return res
def set_proxy(self, host, port, user=None, password=None): def set_proxy(self, host, port, user=None, password=None):
''' '''
Sets the proxy server host and port for the HTTP CONNECT Tunnelling. Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
host: Address of the proxy. Ex: '192.168.0.100' host: Address of the proxy. Ex: '192.168.0.100'
port: Port of the proxy. Ex: 6000 port: Port of the proxy. Ex: 6000
user: User for proxy authorization. user: User for proxy authorization.
password: Password for proxy authorization. password: Password for proxy authorization.
''' '''
self._httpclient.set_proxy(host, port, user, password) self._httpclient.set_proxy(host, port, user, password)
def _get_host(self): def _get_host(self):
if self.use_local_storage: if self.use_local_storage:
return self.dev_host return self.dev_host
else: else:
return self.account_name + self.host_base return self.account_name + self.host_base
def _perform_request_worker(self, request): def _perform_request_worker(self, request):
return self._httpclient.perform_request(request) return self._httpclient.perform_request(request)
def _perform_request(self, request, text_encoding='utf-8'): def _perform_request(self, request, text_encoding='utf-8'):
''' '''
Sends the request and return response. Catches HTTPError and hand it Sends the request and return response. Catches HTTPError and hand it
to error handler to error handler
''' '''
try: try:
if self._batchclient is not None: if self._batchclient is not None:
return self._batchclient.insert_request_to_batch(request) return self._batchclient.insert_request_to_batch(request)
else: else:
resp = self._filter(request) resp = self._filter(request)
if sys.version_info >= (3,) and isinstance(resp, bytes) and \ if sys.version_info >= (3,) and isinstance(resp, bytes) and \
text_encoding: text_encoding:
resp = resp.decode(text_encoding) resp = resp.decode(text_encoding)
except HTTPError as ex: except HTTPError as ex:
_storage_error_handler(ex) _storage_error_handler(ex)
return resp return resp

View File

@@ -1,491 +1,491 @@
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# Copyright (c) Microsoft. All rights reserved. # Copyright (c) Microsoft. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
from azure import ( from azure import (
WindowsAzureError, WindowsAzureError,
TABLE_SERVICE_HOST_BASE, TABLE_SERVICE_HOST_BASE,
DEV_TABLE_HOST, DEV_TABLE_HOST,
_convert_class_to_xml, _convert_class_to_xml,
_convert_response_to_feeds, _convert_response_to_feeds,
_dont_fail_not_exist, _dont_fail_not_exist,
_dont_fail_on_exist, _dont_fail_on_exist,
_get_request_body, _get_request_body,
_int_or_none, _int_or_none,
_parse_response, _parse_response,
_parse_response_for_dict, _parse_response_for_dict,
_parse_response_for_dict_filter, _parse_response_for_dict_filter,
_str, _str,
_str_or_none, _str_or_none,
_update_request_uri_query_local_storage, _update_request_uri_query_local_storage,
_validate_not_none, _validate_not_none,
) )
from azure.http import HTTPRequest from azure.http import HTTPRequest
from azure.http.batchclient import _BatchClient from azure.http.batchclient import _BatchClient
from azure.storage import ( from azure.storage import (
StorageServiceProperties, StorageServiceProperties,
_convert_entity_to_xml, _convert_entity_to_xml,
_convert_response_to_entity, _convert_response_to_entity,
_convert_table_to_xml, _convert_table_to_xml,
_convert_xml_to_entity, _convert_xml_to_entity,
_convert_xml_to_table, _convert_xml_to_table,
_sign_storage_table_request, _sign_storage_table_request,
_update_storage_table_header, _update_storage_table_header,
) )
from azure.storage.storageclient import _StorageClient from azure.storage.storageclient import _StorageClient
class TableService(_StorageClient): class TableService(_StorageClient):
''' '''
This is the main class managing Table resources. This is the main class managing Table resources.
''' '''
def __init__(self, account_name=None, account_key=None, protocol='https', def __init__(self, account_name=None, account_key=None, protocol='https',
host_base=TABLE_SERVICE_HOST_BASE, dev_host=DEV_TABLE_HOST): host_base=TABLE_SERVICE_HOST_BASE, dev_host=DEV_TABLE_HOST):
''' '''
account_name: your storage account name, required for all operations. account_name: your storage account name, required for all operations.
account_key: your storage account key, required for all operations. account_key: your storage account key, required for all operations.
protocol: Optional. Protocol. Defaults to http. protocol: Optional. Protocol. Defaults to http.
host_base: host_base:
Optional. Live host base url. Defaults to Azure url. Override this Optional. Live host base url. Defaults to Azure url. Override this
for on-premise. for on-premise.
dev_host: Optional. Dev host url. Defaults to localhost. dev_host: Optional. Dev host url. Defaults to localhost.
''' '''
super(TableService, self).__init__( super(TableService, self).__init__(
account_name, account_key, protocol, host_base, dev_host) account_name, account_key, protocol, host_base, dev_host)
def begin_batch(self): def begin_batch(self):
if self._batchclient is None: if self._batchclient is None:
self._batchclient = _BatchClient( self._batchclient = _BatchClient(
service_instance=self, service_instance=self,
account_key=self.account_key, account_key=self.account_key,
account_name=self.account_name) account_name=self.account_name)
return self._batchclient.begin_batch() return self._batchclient.begin_batch()
def commit_batch(self): def commit_batch(self):
try: try:
ret = self._batchclient.commit_batch() ret = self._batchclient.commit_batch()
finally: finally:
self._batchclient = None self._batchclient = None
return ret return ret
def cancel_batch(self): def cancel_batch(self):
self._batchclient = None self._batchclient = None
def get_table_service_properties(self): def get_table_service_properties(self):
''' '''
Gets the properties of a storage account's Table service, including Gets the properties of a storage account's Table service, including
Windows Azure Storage Analytics. Windows Azure Storage Analytics.
''' '''
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/?restype=service&comp=properties' request.path = '/?restype=service&comp=properties'
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response(response, StorageServiceProperties) return _parse_response(response, StorageServiceProperties)
def set_table_service_properties(self, storage_service_properties): def set_table_service_properties(self, storage_service_properties):
''' '''
Sets the properties of a storage account's Table Service, including Sets the properties of a storage account's Table Service, including
Windows Azure Storage Analytics. Windows Azure Storage Analytics.
storage_service_properties: StorageServiceProperties object. storage_service_properties: StorageServiceProperties object.
''' '''
_validate_not_none('storage_service_properties', _validate_not_none('storage_service_properties',
storage_service_properties) storage_service_properties)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/?restype=service&comp=properties' request.path = '/?restype=service&comp=properties'
request.body = _get_request_body( request.body = _get_request_body(
_convert_class_to_xml(storage_service_properties)) _convert_class_to_xml(storage_service_properties))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict(response) return _parse_response_for_dict(response)
def query_tables(self, table_name=None, top=None, next_table_name=None): def query_tables(self, table_name=None, top=None, next_table_name=None):
''' '''
Returns a list of tables under the specified account. Returns a list of tables under the specified account.
table_name: Optional. The specific table to query. table_name: Optional. The specific table to query.
top: Optional. Maximum number of tables to return. top: Optional. Maximum number of tables to return.
next_table_name: next_table_name:
Optional. When top is used, the next table name is stored in Optional. When top is used, the next table name is stored in
result.x_ms_continuation['NextTableName'] result.x_ms_continuation['NextTableName']
''' '''
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
if table_name is not None: if table_name is not None:
uri_part_table_name = "('" + table_name + "')" uri_part_table_name = "('" + table_name + "')"
else: else:
uri_part_table_name = "" uri_part_table_name = ""
request.path = '/Tables' + uri_part_table_name + '' request.path = '/Tables' + uri_part_table_name + ''
request.query = [ request.query = [
('$top', _int_or_none(top)), ('$top', _int_or_none(top)),
('NextTableName', _str_or_none(next_table_name)) ('NextTableName', _str_or_none(next_table_name))
] ]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _convert_response_to_feeds(response, _convert_xml_to_table) return _convert_response_to_feeds(response, _convert_xml_to_table)
def create_table(self, table, fail_on_exist=False): def create_table(self, table, fail_on_exist=False):
''' '''
Creates a new table in the storage account. Creates a new table in the storage account.
table: table:
Name of the table to create. Table name may contain only Name of the table to create. Table name may contain only
alphanumeric characters and cannot begin with a numeric character. alphanumeric characters and cannot begin with a numeric character.
It is case-insensitive and must be from 3 to 63 characters long. It is case-insensitive and must be from 3 to 63 characters long.
fail_on_exist: Specify whether throw exception when table exists. fail_on_exist: Specify whether throw exception when table exists.
''' '''
_validate_not_none('table', table) _validate_not_none('table', table)
request = HTTPRequest() request = HTTPRequest()
request.method = 'POST' request.method = 'POST'
request.host = self._get_host() request.host = self._get_host()
request.path = '/Tables' request.path = '/Tables'
request.body = _get_request_body(_convert_table_to_xml(table)) request.body = _get_request_body(_convert_table_to_xml(table))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
if not fail_on_exist: if not fail_on_exist:
try: try:
self._perform_request(request) self._perform_request(request)
return True return True
except WindowsAzureError as ex: except WindowsAzureError as ex:
_dont_fail_on_exist(ex) _dont_fail_on_exist(ex)
return False return False
else: else:
self._perform_request(request) self._perform_request(request)
return True return True
def delete_table(self, table_name, fail_not_exist=False): def delete_table(self, table_name, fail_not_exist=False):
''' '''
table_name: Name of the table to delete. table_name: Name of the table to delete.
fail_not_exist: fail_not_exist:
Specify whether throw exception when table doesn't exist. Specify whether throw exception when table doesn't exist.
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'DELETE' request.method = 'DELETE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/Tables(\'' + _str(table_name) + '\')' request.path = '/Tables(\'' + _str(table_name) + '\')'
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
if not fail_not_exist: if not fail_not_exist:
try: try:
self._perform_request(request) self._perform_request(request)
return True return True
except WindowsAzureError as ex: except WindowsAzureError as ex:
_dont_fail_not_exist(ex) _dont_fail_not_exist(ex)
return False return False
else: else:
self._perform_request(request) self._perform_request(request)
return True return True
def get_entity(self, table_name, partition_key, row_key, select=''): def get_entity(self, table_name, partition_key, row_key, select=''):
''' '''
Get an entity in a table; includes the $select options. Get an entity in a table; includes the $select options.
partition_key: PartitionKey of the entity. partition_key: PartitionKey of the entity.
row_key: RowKey of the entity. row_key: RowKey of the entity.
select: Property names to select. select: Property names to select.
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('partition_key', partition_key) _validate_not_none('partition_key', partition_key)
_validate_not_none('row_key', row_key) _validate_not_none('row_key', row_key)
_validate_not_none('select', select) _validate_not_none('select', select)
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(table_name) + \ request.path = '/' + _str(table_name) + \
'(PartitionKey=\'' + _str(partition_key) + \ '(PartitionKey=\'' + _str(partition_key) + \
'\',RowKey=\'' + \ '\',RowKey=\'' + \
_str(row_key) + '\')?$select=' + \ _str(row_key) + '\')?$select=' + \
_str(select) + '' _str(select) + ''
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _convert_response_to_entity(response) return _convert_response_to_entity(response)
def query_entities(self, table_name, filter=None, select=None, top=None, def query_entities(self, table_name, filter=None, select=None, top=None,
next_partition_key=None, next_row_key=None): next_partition_key=None, next_row_key=None):
''' '''
Get entities in a table; includes the $filter and $select options. Get entities in a table; includes the $filter and $select options.
table_name: Table to query. table_name: Table to query.
filter: filter:
Optional. Filter as described at Optional. Filter as described at
http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx
select: Optional. Property names to select from the entities. select: Optional. Property names to select from the entities.
top: Optional. Maximum number of entities to return. top: Optional. Maximum number of entities to return.
next_partition_key: next_partition_key:
Optional. When top is used, the next partition key is stored in Optional. When top is used, the next partition key is stored in
result.x_ms_continuation['NextPartitionKey'] result.x_ms_continuation['NextPartitionKey']
next_row_key: next_row_key:
Optional. When top is used, the next partition key is stored in Optional. When top is used, the next partition key is stored in
result.x_ms_continuation['NextRowKey'] result.x_ms_continuation['NextRowKey']
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
request = HTTPRequest() request = HTTPRequest()
request.method = 'GET' request.method = 'GET'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(table_name) + '()' request.path = '/' + _str(table_name) + '()'
request.query = [ request.query = [
('$filter', _str_or_none(filter)), ('$filter', _str_or_none(filter)),
('$select', _str_or_none(select)), ('$select', _str_or_none(select)),
('$top', _int_or_none(top)), ('$top', _int_or_none(top)),
('NextPartitionKey', _str_or_none(next_partition_key)), ('NextPartitionKey', _str_or_none(next_partition_key)),
('NextRowKey', _str_or_none(next_row_key)) ('NextRowKey', _str_or_none(next_row_key))
] ]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _convert_response_to_feeds(response, _convert_xml_to_entity) return _convert_response_to_feeds(response, _convert_xml_to_entity)
def insert_entity(self, table_name, entity, def insert_entity(self, table_name, entity,
content_type='application/atom+xml'): content_type='application/atom+xml'):
''' '''
Inserts a new entity into a table. Inserts a new entity into a table.
table_name: Table name. table_name: Table name.
entity: entity:
Required. The entity object to insert. Could be a dict format or Required. The entity object to insert. Could be a dict format or
entity object. entity object.
content_type: Required. Must be set to application/atom+xml content_type: Required. Must be set to application/atom+xml
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('entity', entity) _validate_not_none('entity', entity)
_validate_not_none('content_type', content_type) _validate_not_none('content_type', content_type)
request = HTTPRequest() request = HTTPRequest()
request.method = 'POST' request.method = 'POST'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + _str(table_name) + '' request.path = '/' + _str(table_name) + ''
request.headers = [('Content-Type', _str_or_none(content_type))] request.headers = [('Content-Type', _str_or_none(content_type))]
request.body = _get_request_body(_convert_entity_to_xml(entity)) request.body = _get_request_body(_convert_entity_to_xml(entity))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _convert_response_to_entity(response) return _convert_response_to_entity(response)
def update_entity(self, table_name, partition_key, row_key, entity, def update_entity(self, table_name, partition_key, row_key, entity,
content_type='application/atom+xml', if_match='*'): content_type='application/atom+xml', if_match='*'):
''' '''
Updates an existing entity in a table. The Update Entity operation Updates an existing entity in a table. The Update Entity operation
replaces the entire entity and can be used to remove properties. replaces the entire entity and can be used to remove properties.
table_name: Table name. table_name: Table name.
partition_key: PartitionKey of the entity. partition_key: PartitionKey of the entity.
row_key: RowKey of the entity. row_key: RowKey of the entity.
entity: entity:
Required. The entity object to insert. Could be a dict format or Required. The entity object to insert. Could be a dict format or
entity object. entity object.
content_type: Required. Must be set to application/atom+xml content_type: Required. Must be set to application/atom+xml
if_match: if_match:
Optional. Specifies the condition for which the merge should be Optional. Specifies the condition for which the merge should be
performed. To force an unconditional merge, set to the wildcard performed. To force an unconditional merge, set to the wildcard
character (*). character (*).
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('partition_key', partition_key) _validate_not_none('partition_key', partition_key)
_validate_not_none('row_key', row_key) _validate_not_none('row_key', row_key)
_validate_not_none('entity', entity) _validate_not_none('entity', entity)
_validate_not_none('content_type', content_type) _validate_not_none('content_type', content_type)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(table_name) + '(PartitionKey=\'' + \ _str(table_name) + '(PartitionKey=\'' + \
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')' _str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
request.headers = [ request.headers = [
('Content-Type', _str_or_none(content_type)), ('Content-Type', _str_or_none(content_type)),
('If-Match', _str_or_none(if_match)) ('If-Match', _str_or_none(if_match))
] ]
request.body = _get_request_body(_convert_entity_to_xml(entity)) request.body = _get_request_body(_convert_entity_to_xml(entity))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict_filter(response, filter=['etag']) return _parse_response_for_dict_filter(response, filter=['etag'])
def merge_entity(self, table_name, partition_key, row_key, entity, def merge_entity(self, table_name, partition_key, row_key, entity,
content_type='application/atom+xml', if_match='*'): content_type='application/atom+xml', if_match='*'):
''' '''
Updates an existing entity by updating the entity's properties. This Updates an existing entity by updating the entity's properties. This
operation does not replace the existing entity as the Update Entity operation does not replace the existing entity as the Update Entity
operation does. operation does.
table_name: Table name. table_name: Table name.
partition_key: PartitionKey of the entity. partition_key: PartitionKey of the entity.
row_key: RowKey of the entity. row_key: RowKey of the entity.
entity: entity:
Required. The entity object to insert. Can be a dict format or Required. The entity object to insert. Can be a dict format or
entity object. entity object.
content_type: Required. Must be set to application/atom+xml content_type: Required. Must be set to application/atom+xml
if_match: if_match:
Optional. Specifies the condition for which the merge should be Optional. Specifies the condition for which the merge should be
performed. To force an unconditional merge, set to the wildcard performed. To force an unconditional merge, set to the wildcard
character (*). character (*).
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('partition_key', partition_key) _validate_not_none('partition_key', partition_key)
_validate_not_none('row_key', row_key) _validate_not_none('row_key', row_key)
_validate_not_none('entity', entity) _validate_not_none('entity', entity)
_validate_not_none('content_type', content_type) _validate_not_none('content_type', content_type)
request = HTTPRequest() request = HTTPRequest()
request.method = 'MERGE' request.method = 'MERGE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(table_name) + '(PartitionKey=\'' + \ _str(table_name) + '(PartitionKey=\'' + \
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')' _str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
request.headers = [ request.headers = [
('Content-Type', _str_or_none(content_type)), ('Content-Type', _str_or_none(content_type)),
('If-Match', _str_or_none(if_match)) ('If-Match', _str_or_none(if_match))
] ]
request.body = _get_request_body(_convert_entity_to_xml(entity)) request.body = _get_request_body(_convert_entity_to_xml(entity))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict_filter(response, filter=['etag']) return _parse_response_for_dict_filter(response, filter=['etag'])
def delete_entity(self, table_name, partition_key, row_key, def delete_entity(self, table_name, partition_key, row_key,
content_type='application/atom+xml', if_match='*'): content_type='application/atom+xml', if_match='*'):
''' '''
Deletes an existing entity in a table. Deletes an existing entity in a table.
table_name: Table name. table_name: Table name.
partition_key: PartitionKey of the entity. partition_key: PartitionKey of the entity.
row_key: RowKey of the entity. row_key: RowKey of the entity.
content_type: Required. Must be set to application/atom+xml content_type: Required. Must be set to application/atom+xml
if_match: if_match:
Optional. Specifies the condition for which the delete should be Optional. Specifies the condition for which the delete should be
performed. To force an unconditional delete, set to the wildcard performed. To force an unconditional delete, set to the wildcard
character (*). character (*).
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('partition_key', partition_key) _validate_not_none('partition_key', partition_key)
_validate_not_none('row_key', row_key) _validate_not_none('row_key', row_key)
_validate_not_none('content_type', content_type) _validate_not_none('content_type', content_type)
_validate_not_none('if_match', if_match) _validate_not_none('if_match', if_match)
request = HTTPRequest() request = HTTPRequest()
request.method = 'DELETE' request.method = 'DELETE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(table_name) + '(PartitionKey=\'' + \ _str(table_name) + '(PartitionKey=\'' + \
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')' _str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
request.headers = [ request.headers = [
('Content-Type', _str_or_none(content_type)), ('Content-Type', _str_or_none(content_type)),
('If-Match', _str_or_none(if_match)) ('If-Match', _str_or_none(if_match))
] ]
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
self._perform_request(request) self._perform_request(request)
def insert_or_replace_entity(self, table_name, partition_key, row_key, def insert_or_replace_entity(self, table_name, partition_key, row_key,
entity, content_type='application/atom+xml'): entity, content_type='application/atom+xml'):
''' '''
Replaces an existing entity or inserts a new entity if it does not Replaces an existing entity or inserts a new entity if it does not
exist in the table. Because this operation can insert or update an exist in the table. Because this operation can insert or update an
entity, it is also known as an "upsert" operation. entity, it is also known as an "upsert" operation.
table_name: Table name. table_name: Table name.
partition_key: PartitionKey of the entity. partition_key: PartitionKey of the entity.
row_key: RowKey of the entity. row_key: RowKey of the entity.
entity: entity:
Required. The entity object to insert. Could be a dict format or Required. The entity object to insert. Could be a dict format or
entity object. entity object.
content_type: Required. Must be set to application/atom+xml content_type: Required. Must be set to application/atom+xml
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('partition_key', partition_key) _validate_not_none('partition_key', partition_key)
_validate_not_none('row_key', row_key) _validate_not_none('row_key', row_key)
_validate_not_none('entity', entity) _validate_not_none('entity', entity)
_validate_not_none('content_type', content_type) _validate_not_none('content_type', content_type)
request = HTTPRequest() request = HTTPRequest()
request.method = 'PUT' request.method = 'PUT'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(table_name) + '(PartitionKey=\'' + \ _str(table_name) + '(PartitionKey=\'' + \
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')' _str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
request.headers = [('Content-Type', _str_or_none(content_type))] request.headers = [('Content-Type', _str_or_none(content_type))]
request.body = _get_request_body(_convert_entity_to_xml(entity)) request.body = _get_request_body(_convert_entity_to_xml(entity))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict_filter(response, filter=['etag']) return _parse_response_for_dict_filter(response, filter=['etag'])
def insert_or_merge_entity(self, table_name, partition_key, row_key, def insert_or_merge_entity(self, table_name, partition_key, row_key,
entity, content_type='application/atom+xml'): entity, content_type='application/atom+xml'):
''' '''
Merges an existing entity or inserts a new entity if it does not exist Merges an existing entity or inserts a new entity if it does not exist
in the table. Because this operation can insert or update an entity, in the table. Because this operation can insert or update an entity,
it is also known as an "upsert" operation. it is also known as an "upsert" operation.
table_name: Table name. table_name: Table name.
partition_key: PartitionKey of the entity. partition_key: PartitionKey of the entity.
row_key: RowKey of the entity. row_key: RowKey of the entity.
entity: entity:
Required. The entity object to insert. Could be a dict format or Required. The entity object to insert. Could be a dict format or
entity object. entity object.
content_type: Required. Must be set to application/atom+xml content_type: Required. Must be set to application/atom+xml
''' '''
_validate_not_none('table_name', table_name) _validate_not_none('table_name', table_name)
_validate_not_none('partition_key', partition_key) _validate_not_none('partition_key', partition_key)
_validate_not_none('row_key', row_key) _validate_not_none('row_key', row_key)
_validate_not_none('entity', entity) _validate_not_none('entity', entity)
_validate_not_none('content_type', content_type) _validate_not_none('content_type', content_type)
request = HTTPRequest() request = HTTPRequest()
request.method = 'MERGE' request.method = 'MERGE'
request.host = self._get_host() request.host = self._get_host()
request.path = '/' + \ request.path = '/' + \
_str(table_name) + '(PartitionKey=\'' + \ _str(table_name) + '(PartitionKey=\'' + \
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')' _str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
request.headers = [('Content-Type', _str_or_none(content_type))] request.headers = [('Content-Type', _str_or_none(content_type))]
request.body = _get_request_body(_convert_entity_to_xml(entity)) request.body = _get_request_body(_convert_entity_to_xml(entity))
request.path, request.query = _update_request_uri_query_local_storage( request.path, request.query = _update_request_uri_query_local_storage(
request, self.use_local_storage) request, self.use_local_storage)
request.headers = _update_storage_table_header(request) request.headers = _update_storage_table_header(request)
response = self._perform_request(request) response = self._perform_request(request)
return _parse_response_for_dict_filter(response, filter=['etag']) return _parse_response_for_dict_filter(response, filter=['etag'])
def _perform_request_worker(self, request): def _perform_request_worker(self, request):
auth = _sign_storage_table_request(request, auth = _sign_storage_table_request(request,
self.account_name, self.account_name,
self.account_key) self.account_key)
request.headers.append(('Authorization', auth)) request.headers.append(('Authorization', auth))
return self._httpclient.perform_request(request) return self._httpclient.perform_request(request)