diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07730d27fdbb..057ab8c913b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,6 +111,10 @@ Mypy install and run. **Example: Invoke tox, breaking into the debugger on failure** `tox -e whl -c ../../../eng/tox/tox.ini -- --pdb` +### More Reading + +We maintain an [additional document](doc/eng_sys_checks.md) that has a ton of detail as to what is actually _happening_ in these executions. + ### Dev Feed Daily dev build version of Azure sdk packages for python are available and are uploaded to Azure devops feed daily. We have also created a tox environment to test a package against dev built version of dependent packages. Below is the link to Azure devops feed. [`https://dev.azure.com/azure-sdk/public/_packaging?_a=feed&feed=azure-sdk-for-python`](https://dev.azure.com/azure-sdk/public/_packaging?_a=feed&feed=azure-sdk-for-python) diff --git a/doc/README.md b/doc/README.md index 0756c6aecdfe..b4ecf81e6fd9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,4 +2,6 @@ This folder contains some documentations for this repository: The folder structure is the following - [sphinx](./sphinx) : contains the documentation source code for https://azure.github.io/azure-sdk-for-python/ -- [dev](./dev) : contains advanced documentation for _developers_ of SDK (not _consumers_ of SDK) \ No newline at end of file +- [dev](./dev) : contains advanced documentation for _developers_ of SDK (not _consumers_ of SDK) + +The file [eng_sys_checks](eng_sys_checks.md) is a read up as to what a standard `ci.yml` will actually execute. \ No newline at end of file diff --git a/doc/eng_sys_checks.md b/doc/eng_sys_checks.md new file mode 100644 index 000000000000..81d8e861af2d --- /dev/null +++ b/doc/eng_sys_checks.md @@ -0,0 +1,196 @@ +# Azure SDK for Python - Engineering System + +There are various tests currently enabled in Azure pipeline for Python SDK and some of them are enabled only for nightly CI checks. We also run some static analysis tool to verify code completeness, security and lint check. + +Check the [contributing guide](../CONTRIBUTING.md#building-and-testing) for an intro to `tox`. + +As a contributor, you will see the build jobs run in two modes: `Nightly Scheduled` and `Pull Request`. + +These utilize the _same build definition_, except that the `nightly` builds run additional, deeper checks that run for a bit longer. + +Example PR build: + +![res/job_snippet.png](res/job_snippet.png) + + - `Analyze` tox envs run during the `Analyze job. + - `Test _` runs PR/Nightly tox envs, depending on context. + +## Analyze Checks +Analyze job in both nightly CI and pull request validation pipeline runs a set of static analysis using external and internal tools. Following are the list of these static analysis. + +#### MyPy +`Mypy` is a static analysis tool that runs type checking of python package. Following are the steps to run `MyPy` locally for a specific package +1. Go to root of the package +2. Execute following command + ```tox -e mypy -c ../../../eng/tox/tox.ini ``` + +#### Pylint +`Pylint` is a static analysis tool to run lint checking. Following are the steps to run `pylint` locally for a specific package. + +1. Go to root of the package. +2. Execute following command + ```tox -e pylint -c ../../../eng/tox/tox.ini``` + + +#### Bandit +`Bandit` is static security analysis tool. This check is triggered for all Azure SDK package as part of analyze job. Following are the steps to `Bandit` tool locally for a specific package. + +1. Got to package root directory. +2. Execute following command + ```tox -e bandit -c ../../../eng/tox/tox.ini``` + + +#### ApiStubGen +`ApiStubGen` is an internal tool used to create API stub to help reviewing public APIs in our SDK package using [`APIViewTool`.](https://apiview.dev/) This tool also has some built in lint checks available and purpose of having this step in analyze job is to ensure any change in code is not impacting stubbing process and also to have more and more custom lint checks added in future. + +#### Change log verification + +Change log verification is added to ensure package has valid change log for current version. Guidelines to properly maintain the change log is documented [here]() + +## PR Validation Checks +Each pull request runs various tests using `pytest` in addition to all the tests mentioned above in analyze check. Pull request validation performs 3 different types of test: `whl, sdist and depends`. Following section explains the purpose of each of these tests and how to execute them locally. All pull requests are validated on multiple python versions across different platforms and below is the test matrix for pull request. + + +|`Python Version`|`Platform` | +|--|--| +|2.7|Linux| +|3.5|Windows| +|3.8|Linux| + +### PR validation tox test environments +Tests are executed using tox environment and following are the tox test names that are part of pull request validation +#### whl +This test installs wheel of the package being tested and runs all tests cases in the package using `pytest`. Following is the command to run this test environment locally. + +1. Go to package root folder on a command line +2. Run following command + ``tox -e whl -c ../../../eng/tox/tox.ini`` + +#### sdist +This test installs sdist of the package being tested and runs all tests cases in the package using `pytest`. Following is the command to run this test environment locally. + +1. Go to package root folder on a command line +2. Run following command + ``tox -e sdist -c ../../../eng/tox/tox.ini`` + +####depends +This test is to ensure all modules in the package being tested can be successfully imported. This is to ensure all package requirement is properly set in setup.py as well as to ensure modules are imported using valid namespace. This test install the package and it's required packages and executes `from import *`. For e.g. `from azure.core import *`. + +Following is the command to run this test environment locally. + +1. Go to package root folder on a command line +2. Run following command + ``tox -e sdist -c ../../../eng/tox/tox.ini`` + + +## Nightly CI Checks + +Nightly continuous integration checks run all tests mentioned above in Analyze and Pull request checks in addition to multiple other tests. Nightly CI checks run on all python versions that are supported by Azure SDK packages across multiple platforms. + +![res/full_matrix.png](res/full_matrix.png) + +Regression also executes: +![res/regression.png](res/regression.png) + +Nightly CI check runs following additional tests to ensure the dependency between a package being developed against released packages to ensure backward compatibility. Following is the explanation of why we need dependency tests to ensure backward compatibility. + +Imagine a situation where package `XYZ` requires another package `ABC` and as per the package requirement of `XYZ`, It should work with any version between 1.0 and 2.0 of package `ABC`. + +Package `XYZ` requires package `ABC` + +As a developer of package `XYZ`, we need to ensure that our package works fine with all versions of ABC as long as it is within package requirement specification. + +Another scenario where regression test( reverse dependency) is required. Let's take same example above and assume we are developers of package `ABC` which is taken as required package by another package `XYZ` + +Package `ABC is required by package `XYZ` + + +As a developer of `ABC`, we need to ensure that any new change in `ABC` is not breaking the use of `XYZ` and hence ensures backward compatibility. + +Let's take few Azure SDK packages instead of dummy names to explain this in a context we are more familiar of. + +Most of the Azure SDK packages require `azure-core` and this requirement is within a range for e.g. `azure-storage-blob` that requires `azure-core >1.0.0, <2.0.0`. So any new change in azure-storage-blob needs to make sure it works fine with all versions of azure-core between 1.0.0 and 2.0.0(Both included). +Similarly any new version of azure-core needs to ensure that it is still compatible with all released package versions which takes azure-core as required package. + +It is lot of combinations if we need to run tests for all released versions within the range of requirement specification. In order to reduce the test matrix and at the same time ensures the quality, we currently run the test using oldest released and latest released packages and skips any version in between. + +Following are the additional tests we run during nightly CI checks. + +####Latest Dependency Test + +This test makes sure that a package being developed works absolutely fine using latest released version of required Azure SDK package as long as there is a released version which satisfies the requirement specification. Workflow of this test is as follows: + +1. Identify if any azure SDK package is marked as required package in setup.py of current package being tested. +Note: Any dependency mentioned only in dev_requirements are not considered to identify dependency. +2. Identify latest released version of required azure sdk package on PyPI +3. Install latest released version of required package instead of dev dependency to package in code repo +4. Install current package that is being tested +5. Run pytest of all test cases in current package + +Tox name of this test is `latestdependency` and steps to manually run this test locally is as follows. +1. Go to package root. For e.g azure-storage-blob or azure-identity +2. Run following command + `Tox –e latestdependency –c ../../../tox/tox.ini` + + +####Oldest Dependency Test + +This test makes sure that a package being developed works absolutely fine using oldest released version of required Azure SDK package as long as there is a released version which satisfies the requirement specification. Workflow of this test is as follows: + +1. Identify if any azure SDK package is marked as required package in setup.py of current package being tested. +Note: Any dependency mentioned only in dev_requirements are not considered to identify dependency. +2. Identify oldest released version of required azure sdk package on PyPI +3. Install oldest released version of required package instead of dev dependency to package in code repo +4. Install current package that is being tested +5. Run pytest of all test cases in current package + +Tox name of this test is `mindependency` and steps to manually run this test locally is as follows. +1. Go to package root. For e.g azure-storage-blob or azure-identity +2. Run following command +`Tox –e mindependency –c ../../../tox/tox.ini` + + +####Regression Test + +As mentioned earlier, regression test or reverse dependency test is added to avoid a regression scenario for customers when any new change is made in a package that is required by other packages. Currently we have only very few Azure SDK packages that are added as required package by other Azure SDK package. As of now, list of these required packages are: +`azure-core` +`azure-eventhub` +`azure-storage-blob` + +Our regression framework automatically finds any such package that is added as required package so this list is not hardcoded. + +We have two different set of regression tests to verify regression scenarios against oldest and latest released dependent packages. +• Regression using latest released dependent package +• Regression using oldest released dependent package + +One main difference between regression tests and forward dependency test( latest and mindependency) is in terms of what test cases are executed as part of the tests. While forward dependency tests executes the test cases in current code repo, regression tests execute the tests that were part of repo at the time of dependent package release. To make it more clear, let's look at an example here. + +Let's assume that we are testing regression for azure-core and this test is for regression against latest released dependent packages. Test will identify all packages that takes azure-core as required package and finds latest released version of those packages. Test framework install currently being developed azure-core and latest released dependent package and runs the test cases in dependent package, for e.g. azure-identity, that were part of repo at the time of releasing depending package. + +Workflow of this test is as follows when running regression for an SDK package. +1. Identify any packages that takes currently being tested package as required package +2. Find latest and oldest released versions of dependent package from PyPI +3. Install currently being developed version of package we are testing regression for. E.g. azure-core +4. Checkout the release tag of dependent package from github +5. Install latest/oldest version of dependent package. For e.g. azure-identity +6. Run test cases within dependent package from checked out branch. + + +Steps to manually run regression test locally: +1. Run below command from your git code repo to generate the wheel of package being developed. Currently we have restricted to have prebuilt wheel. +`./scripts/devops_tasks/build_packages.py --service= -d ` +2. Run below command to start regression test locally +`./scripts/devops_tasks/test_regression.py azure-* --service= --whl-dir=` + + +How to run these additional tests on azure pipelines manually + +Following variables can be set at queueing time in order to run these additional tests which are by default run only for scheduled runs. + +• Latest and oldest dependency test in addition to basic testing +Variable name: `Run.DependencyTest` +Value: true + +• Regression test +Variable name: `Run.Regression` +Value: true diff --git a/doc/res/full_matrix.png b/doc/res/full_matrix.png new file mode 100644 index 000000000000..7decc5e8cd01 Binary files /dev/null and b/doc/res/full_matrix.png differ diff --git a/doc/res/job_snippet.png b/doc/res/job_snippet.png new file mode 100644 index 000000000000..e4d7c47a391a Binary files /dev/null and b/doc/res/job_snippet.png differ diff --git a/doc/res/regression.png b/doc/res/regression.png new file mode 100644 index 000000000000..747008591b5a Binary files /dev/null and b/doc/res/regression.png differ diff --git a/sdk/anomalydetector/azure-ai-anomalydetector/MANIFEST.in b/sdk/anomalydetector/azure-ai-anomalydetector/MANIFEST.in index bde85ca9d6c8..622490cdae53 100644 --- a/sdk/anomalydetector/azure-ai-anomalydetector/MANIFEST.in +++ b/sdk/anomalydetector/azure-ai-anomalydetector/MANIFEST.in @@ -1,4 +1,5 @@ recursive-include tests *.py *.yaml +recursive-include samples *.py *.md include *.md include azure/__init__.py include azure/ai/__init__.py diff --git a/sdk/anomalydetector/azure-ai-anomalydetector/samples/README.md b/sdk/anomalydetector/azure-ai-anomalydetector/samples/README.md new file mode 100644 index 000000000000..d3cbeb03df82 --- /dev/null +++ b/sdk/anomalydetector/azure-ai-anomalydetector/samples/README.md @@ -0,0 +1,59 @@ +--- +page_type: sample +languages: + - python +products: + - azure + - azure-cognitive-services + - azure-anomaly-detector +urlFragment: anomalydetector-samples +--- + +# Samples for Azure Anomaly Detector client library for Python + +These code samples show common scenario operations with the Anomaly Detector client library. + +These sample programs show common scenarios for the Anomaly Detector client's offerings. + +|**File Name**|**Description**| +|----------------|-------------| +|[sample_detect_entire_series_anomaly.py][sample_detect_entire_series_anomaly] |Detecting anomalies in the entire time series.| +|[sample_detect_last_point_anomaly.py][sample_detect_last_point_anomaly] |Detecting the anomaly status of the latest data point.| +|[sample_detect_change_point.py][sample_detect_change_point] |Detecting change points in the entire time series.| + +## Prerequisites +* Python 2.7 or 3.5 or higher is required to use this package. +* The Pandas data analysis library. +* You must have an [Azure subscription][azure_subscription] and an +[Azure Anomaly Detector account][azure_anomaly_detector_account] to run these samples. + +## Setup + +1. Install the Azure Anomaly Detector client library for Python with [pip][pip]: + +```bash +pip install azure-ai-anomalydetector +``` + +2. Clone or download this sample repository +3. Open the sample folder in Visual Studio Code or your IDE of choice. + +## Running the samples + +1. Open a terminal window and `cd` to the directory that the samples are saved in. +2. Set the environment variables specified in the sample file you wish to run. +3. Follow the usage described in the file, e.g. `python sample_detect_entire_series_anomaly.py` + +## Next steps + +Check out the [API reference documentation][python-fr-ref-docs] to learn more about +what you can do with the Azure Anomaly Detector client library. + +[pip]: https://pypi.org/project/pip/ +[azure_subscription]: https://azure.microsoft.com/free/cognitive-services +[azure_anomaly_detector_account]: https://ms.portal.azure.com/#create/Microsoft.CognitiveServicesAnomalyDetector +[python-fr-ref-docs]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cognitiveservices-anomalydetector/0.3.0/index.html + +[sample_detect_entire_series_anomaly]: ./sample_detect_entire_series_anomaly.py +[sample_detect_last_point_anomaly]: ./sample_detect_last_point_anomaly.py +[sample_detect_change_point]: ./sample_detect_change_point.py diff --git a/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_data/request-data.csv b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_data/request-data.csv new file mode 100644 index 000000000000..7f242c3712c1 --- /dev/null +++ b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_data/request-data.csv @@ -0,0 +1,47 @@ +2018-03-01T00:00:00Z,32858923 +2018-03-02T00:00:00Z,29615278 +2018-03-03T00:00:00Z,22839355 +2018-03-04T00:00:00Z,25948736 +2018-03-05T00:00:00Z,34139159 +2018-03-06T00:00:00Z,33843985 +2018-03-07T00:00:00Z,33637661 +2018-03-08T00:00:00Z,32627350 +2018-03-09T00:00:00Z,29881076 +2018-03-10T00:00:00Z,22681575 +2018-03-11T00:00:00Z,24629393 +2018-03-12T00:00:00Z,34010679 +2018-03-13T00:00:00Z,33893888 +2018-03-14T00:00:00Z,33760076 +2018-03-15T00:00:00Z,33093515 +2018-03-16T00:00:00Z,29945555 +2018-03-17T00:00:00Z,22676212 +2018-03-18T00:00:00Z,25262514 +2018-03-19T00:00:00Z,33631649 +2018-03-20T00:00:00Z,34468310 +2018-03-21T00:00:00Z,34212281 +2018-03-22T00:00:00Z,38144434 +2018-03-23T00:00:00Z,34662949 +2018-03-24T00:00:00Z,24623684 +2018-03-25T00:00:00Z,26530491 +2018-03-26T00:00:00Z,35445003 +2018-03-27T00:00:00Z,34250789 +2018-03-28T00:00:00Z,33423012 +2018-03-29T00:00:00Z,30744783 +2018-03-30T00:00:00Z,25825128 +2018-03-31T00:00:00Z,21244209 +2018-04-01T00:00:00Z,22576956 +2018-04-02T00:00:00Z,31957221 +2018-04-03T00:00:00Z,33841228 +2018-04-04T00:00:00Z,33554483 +2018-04-05T00:00:00Z,32383350 +2018-04-06T00:00:00Z,29494850 +2018-04-07T00:00:00Z,22815534 +2018-04-08T00:00:00Z,25557267 +2018-04-09T00:00:00Z,34858252 +2018-04-10T00:00:00Z,34750597 +2018-04-11T00:00:00Z,34717956 +2018-04-12T00:00:00Z,34132534 +2018-04-13T00:00:00Z,30762236 +2018-04-14T00:00:00Z,22504059 +2018-04-15T00:00:00Z,26149060 +2018-04-16T00:00:00Z,35250105 \ No newline at end of file diff --git a/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_change_point.py b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_change_point.py new file mode 100644 index 000000000000..f2f0eb64e6d3 --- /dev/null +++ b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_change_point.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +""" +FILE: sample_detect_change_point.py + +DESCRIPTION: + This sample demonstrates how to detect entire series change points. + +Prerequisites: + * The Anomaly Detector client library for Python + * A .csv file containing a time-series data set with + UTC-timestamp and numerical values pairings. + Example data is included in this repo. + +USAGE: + python sample_detect_change_point.py + + Set the environment variables with your own values before running the sample: + 1) ANOMALY_DETECTOR_KEY - your source Form Anomaly Detector API key. + 2) ANOMALY_DETECTOR_ENDPOINT - the endpoint to your source Anomaly Detector resource. +""" + +import os +from azure.ai.anomalydetector import AnomalyDetectorClient +from azure.ai.anomalydetector.models import DetectRequest, TimeSeriesPoint, TimeGranularity, \ + AnomalyDetectorError +from azure.core.credentials import AzureKeyCredential +import pandas as pd + + +class DetectChangePointsSample(object): + + def detect_change_point(self): + SUBSCRIPTION_KEY = os.environ["ANOMALY_DETECTOR_KEY"] + ANOMALY_DETECTOR_ENDPOINT = os.environ["ANOMALY_DETECTOR_ENDPOINT"] + TIME_SERIES_DATA_PATH = os.path.join("./sample_data", "request-data.csv") + + # Create an Anomaly Detector client + + # + client = AnomalyDetectorClient(AzureKeyCredential(SUBSCRIPTION_KEY), ANOMALY_DETECTOR_ENDPOINT) + # + + # Load in the time series data file + + # + series = [] + data_file = pd.read_csv(TIME_SERIES_DATA_PATH, header=None, encoding='utf-8', parse_dates=[0]) + for index, row in data_file.iterrows(): + series.append(TimeSeriesPoint(timestamp=row[0], value=row[1])) + # + + # Create a request from the data file + + # + request = DetectRequest(series=series, granularity=TimeGranularity.daily) + # + + # detect change points throughout the entire time series + + # + print('Detecting change points in the entire time series.') + + try: + response = client.detect_change_point(request) + except AnomalyDetectorError as e: + print('Error code: {}'.format(e.error.code), 'Error message: {}'.format(e.error.message)) + except Exception as e: + print(e) + + if any(response.is_change_point): + print('An change point was detected at index:') + for i, value in enumerate(response.is_change_point): + if value: + print(i) + else: + print('No change point were detected in the time series.') + # + + +if __name__ == '__main__': + sample = DetectChangePointsSample() + sample.detect_change_point() diff --git a/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_entire_series_anomaly.py b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_entire_series_anomaly.py new file mode 100644 index 000000000000..941e381d9cd0 --- /dev/null +++ b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_entire_series_anomaly.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +""" +FILE: sample_detect_entire_series_anomaly.py + +DESCRIPTION: + This sample demonstrates how to detect entire series anomalies. + +Prerequisites: + * The Anomaly Detector client library for Python + * A .csv file containing a time-series data set with + UTC-timestamp and numerical values pairings. + Example data is included in this repo. + +USAGE: + python sample_detect_entire_series_anomaly.py + + Set the environment variables with your own values before running the sample: + 1) ANOMALY_DETECTOR_KEY - your source Form Anomaly Detector API key. + 2) ANOMALY_DETECTOR_ENDPOINT - the endpoint to your source Anomaly Detector resource. +""" + +import os +from azure.ai.anomalydetector import AnomalyDetectorClient +from azure.ai.anomalydetector.models import DetectRequest, TimeSeriesPoint, TimeGranularity, \ + AnomalyDetectorError +from azure.core.credentials import AzureKeyCredential +import pandas as pd + + +class DetectEntireAnomalySample(object): + + def detect_entire_series(self): + SUBSCRIPTION_KEY = os.environ["ANOMALY_DETECTOR_KEY"] + ANOMALY_DETECTOR_ENDPOINT = os.environ["ANOMALY_DETECTOR_ENDPOINT"] + TIME_SERIES_DATA_PATH = os.path.join("./sample_data", "request-data.csv") + + # Create an Anomaly Detector client + + # + client = AnomalyDetectorClient(AzureKeyCredential(SUBSCRIPTION_KEY), ANOMALY_DETECTOR_ENDPOINT) + # + + # Load in the time series data file + + # + series = [] + data_file = pd.read_csv(TIME_SERIES_DATA_PATH, header=None, encoding='utf-8', parse_dates=[0]) + for index, row in data_file.iterrows(): + series.append(TimeSeriesPoint(timestamp=row[0], value=row[1])) + # + + # Create a request from the data file + + # + request = DetectRequest(series=series, granularity=TimeGranularity.daily) + # + + # detect anomalies throughout the entire time series, as a batch + + # + print('Detecting anomalies in the entire time series.') + + try: + response = client.detect_entire_series(request) + except AnomalyDetectorError as e: + print('Error code: {}'.format(e.error.code), 'Error message: {}'.format(e.error.message)) + except Exception as e: + print(e) + + if any(response.is_anomaly): + print('An anomaly was detected at index:') + for i, value in enumerate(response.is_anomaly): + if value: + print(i) + else: + print('No anomalies were detected in the time series.') + # + + +if __name__ == '__main__': + sample = DetectEntireAnomalySample() + sample.detect_entire_series() diff --git a/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_last_point_anomaly.py b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_last_point_anomaly.py new file mode 100644 index 000000000000..d8557583396c --- /dev/null +++ b/sdk/anomalydetector/azure-ai-anomalydetector/samples/sample_detect_last_point_anomaly.py @@ -0,0 +1,81 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +""" +FILE: sample_detect_last_point_anomaly.py + +DESCRIPTION: + This sample demonstrates how to detect last series point whether is anomaly. + +Prerequisites: + * The Anomaly Detector client library for Python + * A .csv file containing a time-series data set with + UTC-timestamp and numerical values pairings. + Example data is included in this repo. + +USAGE: + python sample_detect_last_point_anomaly.py + + Set the environment variables with your own values before running the sample: + 1) ANOMALY_DETECTOR_KEY - your source Form Anomaly Detector API key. + 2) ANOMALY_DETECTOR_ENDPOINT - the endpoint to your source Anomaly Detector resource. +""" + +import os +from azure.ai.anomalydetector import AnomalyDetectorClient +from azure.ai.anomalydetector.models import DetectRequest, TimeSeriesPoint, TimeGranularity, \ + AnomalyDetectorError +from azure.core.credentials import AzureKeyCredential +import pandas as pd + + +class DetectLastAnomalySample(object): + + def detect_last_point(self): + SUBSCRIPTION_KEY = os.environ["ANOMALY_DETECTOR_KEY"] + ANOMALY_DETECTOR_ENDPOINT = os.environ["ANOMALY_DETECTOR_ENDPOINT"] + TIME_SERIES_DATA_PATH = os.path.join("./sample_data", "request-data.csv") + + # Create an Anomaly Detector client + + # + client = AnomalyDetectorClient(AzureKeyCredential(SUBSCRIPTION_KEY), ANOMALY_DETECTOR_ENDPOINT) + # + + # Load in the time series data file + + # + series = [] + data_file = pd.read_csv(TIME_SERIES_DATA_PATH, header=None, encoding='utf-8', parse_dates=[0]) + for index, row in data_file.iterrows(): + series.append(TimeSeriesPoint(timestamp=row[0], value=row[1])) + # + + # Create a request from the data file + + # + request = DetectRequest(series=series, granularity=TimeGranularity.daily) + # + + # Detect the anomaly status of the latest data point + + # + print('Detecting the anomaly status of the latest data point.') + + try: + response = client.detect_last_point(request) + except AnomalyDetectorError as e: + print('Error code: {}'.format(e.error.code), 'Error message: {}'.format(e.error.message)) + except Exception as e: + print(e) + + if response.is_anomaly: + print('The latest point is detected as anomaly.') + else: + print('The latest point is not detected as anomaly.') + # + + +if __name__ == '__main__': + sample = DetectLastAnomalySample() + sample.detect_last_point() diff --git a/sdk/core/azure-mgmt-core/azure/mgmt/core/tools.py b/sdk/core/azure-mgmt-core/azure/mgmt/core/tools.py index 18502db70977..ce671e572516 100644 --- a/sdk/core/azure-mgmt-core/azure/mgmt/core/tools.py +++ b/sdk/core/azure-mgmt-core/azure/mgmt/core/tools.py @@ -30,13 +30,13 @@ _LOGGER = logging.getLogger(__name__) _ARMID_RE = re.compile( - "(?i)/subscriptions/(?P[^/]*)(/resourceGroups/(?P[^/]*))?" - "(/providers/(?P[^/]*)/(?P[^/]*)/(?P[^/]*)(?P.*))?" + "(?i)/subscriptions/(?P[^/]+)(/resourceGroups/(?P[^/]+))?" + "(/providers/(?P[^/]+)/(?P[^/]*)/(?P[^/]+)(?P.*))?" ) _CHILDREN_RE = re.compile( - "(?i)(/providers/(?P[^/]*))?/" - "(?P[^/]*)/(?P[^/]*)" + "(?i)(/providers/(?P[^/]+))?/" + "(?P[^/]*)/(?P[^/]+)" ) _ARMNAME_RE = re.compile("^[^<>%&:\\?/]{1,260}$") diff --git a/sdk/core/azure-mgmt-core/tests/test_tools.py b/sdk/core/azure-mgmt-core/tests/test_tools.py index 871b78bd754f..831bf9b4835c 100644 --- a/sdk/core/azure-mgmt-core/tests/test_tools.py +++ b/sdk/core/azure-mgmt-core/tests/test_tools.py @@ -239,7 +239,9 @@ def test_resource_parse(self): invalid_ids = [ '/subscriptions/fakesub/resourceGroups/myRg/type1/name1', '/subscriptions/fakesub/resourceGroups/myRg/providers/Microsoft.Provider/foo', - '/subscriptions/fakesub/resourceGroups/myRg/providers/namespace/type/name/type1' + '/subscriptions/fakesub/resourceGroups/myRg/providers/namespace/type/name/type1', + '/subscriptions/fakesub/resourceGroups/', + '/subscriptions//resourceGroups/' ] for invalid_id in invalid_ids: self.assertFalse(is_valid_resource_id(invalid_id)) diff --git a/sdk/eventgrid/azure-eventgrid/CHANGELOG.md b/sdk/eventgrid/azure-eventgrid/CHANGELOG.md index a4a05abba5b4..d44b57ff8e4b 100644 --- a/sdk/eventgrid/azure-eventgrid/CHANGELOG.md +++ b/sdk/eventgrid/azure-eventgrid/CHANGELOG.md @@ -1,8 +1,12 @@ # Release History -## 2.0.0b1 (Unreleased) +## 2.0.0b1 (2020-09-08) - - Placeholder - NEEDS TO BE CHANGED + **Features** + - Version (2.0.0b1) is the first preview of our efforts to create a user-friendly and Pythonic client library for Azure EventGrid. + For more information about this, and preview releases of other Azure SDK libraries, please visit https://azure.github.io/azure-sdk/releases/latest/python.html. + - Implements the `EventGridPublisherClient` for the publish flow for EventGrid Events, CloudEvents and CustomEvents. + - Implements the `EventGridConsumer` for the consume flow of the events. ## 1.3.0 (2019-05-20) diff --git a/sdk/eventgrid/azure-eventgrid/README.md b/sdk/eventgrid/azure-eventgrid/README.md index 67a40b44a789..bb507a877135 100644 --- a/sdk/eventgrid/azure-eventgrid/README.md +++ b/sdk/eventgrid/azure-eventgrid/README.md @@ -1,21 +1,244 @@ -# Microsoft Azure SDK for Python +# Azure EventGrid client library for Python -This is the Microsoft Azure Event Grid Client Library. -This package has been tested with Python 2.7, 3.5, 3.6, 3.7 and 3.8. -For a more complete view of Azure libraries, see the [azure sdk python release](https://aka.ms/azsdk/python/all). +Azure Event Grid is a fully-managed intelligent event routing service that allows for uniform event consumption using a publish-subscribe model. +[Source code][python-eg-src] | [Package (PyPI)][python-eg-pypi] | [API reference documentation][python-eg-ref-docs]| [Product documentation][python-eg-product-docs] | [Samples][python-eg-samples]| [Changelog][python-eg-changelog] -# Usage +## Getting started -For code examples, see [Event Grid](https://docs.microsoft.com/python/api/overview/azure/event-grid) -on docs.microsoft.com. +### Prerequisites +* Python 2.7, or 3.5 or later is required to use this package. +* You must have an [Azure subscription][azure_subscription] and an EventGrid Topic resource to use this package. +### Install the package +Install the Azure EventGrid client library for Python with [pip][pip]: -# Provide Feedback +```bash +pip install azure-eventgrid +``` -If you encounter any bugs or have suggestions, please file an issue in the -[Issues](https://github.com/Azure/azure-sdk-for-python/issues) -section of the project. +#### Create an EventGrid Topic +You can create the resource using [Azure Portal][azure_portal_create_EG_resource] +### Authenticate the client +In order to interact with the Eventgrid service, you will need to create an instance of a client. +A **topic_hostname** and **credential** are necessary to instantiate the client object. -![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-python%2Fazure-eventgrid%2FREADME.png) +#### Looking up the endpoint +You can find the endpoint and the hostname on the Azure portal. + +#### Create the client with AzureKeyCredential + +To use an API key as the `credential` parameter, +pass the key as a string into an instance of [AzureKeyCredential][azure-key-credential]. + +```python +from azure.core.credentials import AzureKeyCredential +from azure.eventgrid import EventGridPublisherClient + +topic_hostname = "https://..eventgrid.azure.net" +credential = AzureKeyCredential("") +eg_publisher_client = EventGridPublisherClient(topic_hostname, credential) +``` + +## Key concepts + +Information about the key concepts on EventGrid, see [Concepts in Azure Event Grid][publisher-service-doc] + +### EventGridPublisherClient +`EventGridPublisherClient` provides operations to send event data to topic hostname specified during client initialization. +Either a list or a single instance of CloudEvent/EventGridEvent/CustomEvent can be sent. + +### EventGridConsumer +`EventGridConsumer` is used to desrialize an event received. + +## Examples + +The following sections provide several code snippets covering some of the most common EventGrid tasks, including: + +* [Send an EventGrid Event](#send-an-eventgrid-event) +* [Send a Cloud Event](#send-a-cloud-event) +* [Consume an eventgrid Event](#consume-an-eventgrid-event) +* [Consume a cloud Event](#consume-a-cloud-event) + +### Send an EventGrid Event + +This example publishes an EventGrid event. + +```Python +import os +from azure.core.credentials import AzureKeyCredential +from azure.eventgrid import EventGridPublisherClient, EventGridEvent + +key = os.environ["EG_ACCESS_KEY"] +topic_hostname = os.environ["EG_TOPIC_HOSTNAME"] + +event = EventGridEvent( + subject="Door1", + data={"team": "azure-sdk"}, + event_type="Azure.Sdk.Demo", + data_version="2.0" +) + +credential = AzureKeyCredential(key) +client = EventGridPublisherClient(topic_hostname, credential) + +client.send(event) +``` + +### Send a Cloud Event + +This example publishes a Cloud event. + +```Python +import os +from azure.core.credentials import AzureKeyCredential +from azure.eventgrid import EventGridPublisherClient, CloudEvent + +key = os.environ["CLOUD_ACCESS_KEY"] +topic_hostname = os.environ["CLOUD_TOPIC_HOSTNAME"] + +event = CloudEvent( + type="Azure.Sdk.Sample", + source="https://egsample.dev/sampleevent", + data={"team": "azure-sdk"} +) + +credential = AzureKeyCredential(key) +client = EventGridPublisherClient(topic_hostname, credential) + +client.send(event) +``` + +### Consume an EventGrid Event + +This example demonstrates consuming and deserializing an eventgrid event. + +```Python +import os +from azure.eventgrid import EventGridConsumer + +consumer = EventGridConsumer() + +eg_storage_dict = { + "id":"bbab625-dc56-4b22-abeb-afcc72e5290c", + "subject":"/blobServices/default/containers/oc2d2817345i200097container/blobs/oc2d2817345i20002296blob", + "data":{ + "api":"PutBlockList", + }, + "eventType":"Microsoft.Storage.BlobCreated", + "dataVersion":"2.0", + "metadataVersion":"1", + "eventTime":"2020-08-07T02:28:23.867525Z", + "topic":"/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.EventGrid/topics/eventgridegsub" +} + +deserialized_event = consumer.decode_eventgrid_event(eg_storage_dict) + +# both allow access to raw properties as strings +time_string = deserialized_event.time +time_string = deserialized_event["time"] +``` + +### Consume a Cloud Event + +This example demonstrates consuming and deserializing a cloud event. + +```Python +import os +from azure.eventgrid import EventGridConsumer + +consumer = EventGridConsumer() + +cloud_storage_dict = { + "id":"a0517898-9fa4-4e70-b4a3-afda1dd68672", + "source":"/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-account}", + "data":{ + "api":"PutBlockList", + }, + "type":"Microsoft.Storage.BlobCreated", + "time":"2020-08-07T01:11:49.765846Z", + "specversion":"1.0" +} + +deserialized_event = consumer.decode_cloud_event(cloud_storage_dict) + +# both allow access to raw properties as strings +time_string = deserialized_event.time +time_string = deserialized_event["time"] +``` + +## Troubleshooting + +- Enable `azure.eventgrid` logger to collect traces from the library. + +### General +Eventgrid client library will raise exceptions defined in [Azure Core][azure_core_exceptions]. + +### Logging +This library uses the standard +[logging][python_logging] library for logging. +Basic information about HTTP sessions (URLs, headers, etc.) is logged at INFO +level. + +### Optional Configuration + +Optional keyword arguments can be passed in at the client and per-operation level. +The azure-core [reference documentation][azure_core_ref_docs] +describes available configurations for retries, logging, transport protocols, and more. + +## Next steps + +The following section provides several code snippets illustrating common patterns used in the EventGrid Python API. + +### More sample code + +These code samples show common champion scenario operations with the Azure Eventgrid client library. + +* Publish Custom Events to a topic: [cs1_publish_custom_events_to_a_topic.py][python-eg-sample-customevent] +* Publish Custom events to a domain topic: [cs2_publish_custom_events_to_a_domain_topic.py][python-eg-sample-customevent-to-domain] +* Deserialize a System Event: [cs3_consume_system_events.py][python-eg-sample-consume-systemevent] +* Deserialize a Custom Event: [cs4_consume_custom_events.py][python-eg-sample-consume-customevent] +* Deserialize a Cloud Event: [cs5_consume_events_using_cloud_events_1.0_schema.py][python-eg-sample-consume-cloudevent] +* Publish a Cloud Event: [cs6_publish_events_using_cloud_events_1.0_schema.py][python-eg-sample-send-cloudevent] + +More samples can be found [here][python-eg-samples]. + +### Additional documentation + +For more extensive documentation on Azure EventGrid, see the [Eventgrid documentation][python-eg-product-docs] on docs.microsoft.com. + +## Contributing +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [cla.microsoft.com][cla]. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct][code_of_conduct]. For more information see the [Code of Conduct FAQ][coc_faq] or contact [opencode@microsoft.com][coc_contact] with any additional questions or comments. + + + +[python-eg-src]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/ +[python-eg-pypi]: https://pypi.org/project/azure-eventgrid +[python-eg-product-docs]: https://docs.microsoft.com/en-us/azure/event-grid/overview +[python-eg-ref-docs]: https://aka.ms/azsdk/python/eventgrid/docs +[python-eg-samples]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/eventgrid/azure-eventgrid/samples +[python-eg-changelog]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/eventgrid/azure-eventgrid/CHANGELOG.md + +[azure_portal_create_EG_resource]: https://ms.portal.azure.com/#blade/HubsExtension/BrowseResource/resourceType/Microsoft.EventGrid%2Ftopics +[azure-key-credential]: https://aka.ms/azsdk/python/core/azurekeycredential +[azure_core_exceptions]: https://aka.ms/azsdk/python/core/docs#module-azure.core.exceptions +[python_logging]: https://docs.python.org/3/library/logging.html +[azure_core_ref_docs]: https://aka.ms/azsdk/python/core/docs + +[python-eg-sample-customevent]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/samples/champion_scenarios/cs1_publish_custom_events_to_a_topic.py +[python-eg-sample-customevent-to-domain]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/samples/champion_scenarios/cs2_publish_custom_events_to_a_domain_topic.py +[python-eg-sample-consume-systemevent]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/samples/champion_scenarios/cs3_consume_system_events.py +[python-eg-sample-consume-customevent]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/samples/champion_scenarios/cs4_consume_custom_events.py +[python-eg-sample-send-cloudevent]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/samples/champion_scenarios/cs5_publish_events_using_cloud_events_1.0_schema.py +[python-eg-sample-consume-cloudevent]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/eventgrid/azure-eventgrid/samples/champion_scenarios/cs6_consume_events_using_cloud_events_1.0_schema.py +[publisher-service-doc]: https://docs.microsoft.com/en-us/azure/event-grid/concepts + +[cla]: https://cla.microsoft.com +[code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ +[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/ +[coc_contact]: mailto:opencode@microsoft.com diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_consumer.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_consumer.py index def40ea69c4b..80cf18d9c859 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_consumer.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_consumer.py @@ -32,9 +32,9 @@ def deserialize_event(self, event, **kwargs): """Single event following CloudEvent/EventGridEvent schema will be parsed and returned as DeserializedEvent. :param event: The event to be deserialized. :type event: Union[str, dict, bytes] + :keyword str encoding: The encoding that should be used. Defaults to 'utf-8' :rtype: models.DeserializedEvent - - :raise: :class:`ValueError`, when events do not follow CloudEvent or EventGridEvent schema. + :raises: :class:`ValueError`, when events do not follow CloudEvent or EventGridEvent schema. """ encode = kwargs.pop('encoding', 'utf-8') try: diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py index a0e5ff7e62c8..7e06364f4668 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py @@ -51,12 +51,12 @@ def send(self, events, **kwargs): # type: (SendType, Any) -> None """Sends event data to topic hostname specified during client initialization. - :param events: A list of CloudEvent/EventGridEvent/CustomEvent to be sent. - :type events: Union[List[models.CloudEvent], List[models.EventGridEvent], List[models.CustomEvent]] + :param events: A list or an instance of CloudEvent/EventGridEvent/CustomEvent to be sent. + :type events: SendType :keyword str content_type: The type of content to be used to send the events. Has default value "application/json; charset=utf-8" for EventGridEvents, with "cloudevents-batch+json" for CloudEvents :rtype: None - :raise: :class:`ValueError`, when events do not follow specified SendType. + :raises: :class:`ValueError`, when events do not follow specified SendType. """ if not isinstance(events, list): events = [events] diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py index f4b552b0882e..48c5df022af8 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py @@ -52,12 +52,12 @@ async def send(self, events, **kwargs): # type: (SendType) -> None """Sends event data to topic hostname specified during client initialization. - :param events: A list of CloudEvent/EventGridEvent/CustomEvent to be sent. - :type events: Union[List[models.CloudEvent], List[models.EventGridEvent], List[models.CustomEvent]] + :param events: A list or an instance of CloudEvent/EventGridEvent/CustomEvent to be sent. + :type events: SendType :keyword str content_type: The type of content to be used to send the events. Has default value "application/json; charset=utf-8" for EventGridEvents, with "cloudevents-batch+json" for CloudEvents :rtype: None - :raise: :class:`ValueError`, when events do not follow specified SendType. + :raises: :class:`ValueError`, when events do not follow specified SendType. """ if not isinstance(events, list): events = [events] diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index a2f95e2cdde2..1de29521c222 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -8,6 +8,11 @@ - `DefaultAzureCredential` allows specifying the client ID of a user-assigned managed identity via keyword argument `managed_identity_client_id` ([#12991](https://github.com/Azure/azure-sdk-for-python/issues/12991)) +- `CertificateCredential` supports Subject Name/Issuer authentication when + created with `send_certificate=True`. The async `CertificateCredential` + (`azure.identity.aio.CertificateCredential`) will support this in a + future version. + ([#10816](https://github.com/Azure/azure-sdk-for-python/issues/10816)) ## 1.4.0 (2020-08-10) ### Added diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py b/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py index 405a0954e6c0..1b8a64fb98f7 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py @@ -29,6 +29,8 @@ class CertificateCredential(ClientCredentialBase): :keyword password: The certificate's password. If a unicode string, it will be encoded as UTF-8. If the certificate requires a different encoding, pass appropriately encoded bytes instead. :paramtype password: str or bytes + :keyword bool send_certificate: if True, the credential will send public certificate material with token requests. + This is required to use Subject Name/Issuer (SNI) authentication. Defaults to False. :keyword bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache. Defaults to False. :keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache when encryption @@ -54,9 +56,30 @@ def __init__(self, tenant_id, client_id, certificate_path, **kwargs): # TODO: msal doesn't formally support passwords (but soon will); the below depends on an implementation detail private_key = serialization.load_pem_private_key(pem_bytes, password=password, backend=default_backend()) + client_credential = {"private_key": private_key, "thumbprint": hexlify(fingerprint).decode("utf-8")} + if kwargs.pop("send_certificate", False): + try: + # the JWT needs the whole chain but load_pem_x509_certificate deserializes only the signing cert + chain = extract_cert_chain(pem_bytes) + client_credential["public_certificate"] = six.ensure_str(chain) + except ValueError as ex: + # we shouldn't land here, because load_pem_private_key should have raised when given a malformed file + message = 'Found no PEM encoded certificate in "{}"'.format(certificate_path) + six.raise_from(ValueError(message), ex) + super(CertificateCredential, self).__init__( - client_id=client_id, - client_credential={"private_key": private_key, "thumbprint": hexlify(fingerprint).decode("utf-8")}, - tenant_id=tenant_id, - **kwargs + client_id=client_id, client_credential=client_credential, tenant_id=tenant_id, **kwargs ) + + +def extract_cert_chain(pem_bytes): + # type: (bytes) -> bytes + """Extract a certificate chain from a PEM file's bytes, removing line breaks.""" + + # if index raises ValueError, there's no PEM-encoded cert + start = pem_bytes.index(b"-----BEGIN CERTIFICATE-----") + footer = b"-----END CERTIFICATE-----" + end = pem_bytes.rindex(footer) + chain = pem_bytes[start:end + len(footer) + 1] + + return b"".join(chain.splitlines()) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py index 4e226bc0c357..c8603b662a6d 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/interactive.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/interactive.py @@ -12,7 +12,7 @@ from typing import TYPE_CHECKING import msal -from six.moves.urllib_parse import urlparse +import six from azure.core.credentials import AccessToken from azure.core.exceptions import ClientAuthenticationError @@ -57,16 +57,27 @@ def _build_auth_record(response): # MSAL uses the subject claim as home_account_id when the STS doesn't provide client_info home_account_id = id_token["sub"] + # "iss" is the URL of the issuing tenant e.g. https://authority/tenant + issuer = six.moves.urllib_parse.urlparse(id_token["iss"]) + + # tenant which issued the token, not necessarily user's home tenant + tenant_id = id_token.get("tid") or issuer.path.strip("/") + + # AAD returns "preferred_username", ADFS returns "upn" + username = id_token.get("preferred_username") or id_token["upn"] + return AuthenticationRecord( - authority=urlparse(id_token["iss"]).netloc, # "iss" is the URL of the issuing tenant + authority=issuer.netloc, client_id=id_token["aud"], home_account_id=home_account_id, - tenant_id=id_token["tid"], # tenant which issued the token, not necessarily user's home tenant - username=id_token["preferred_username"], + tenant_id=tenant_id, + username=username, + ) + except (KeyError, ValueError) as ex: + auth_error = ClientAuthenticationError( + message="Failed to build AuthenticationRecord from unexpected identity token" ) - except (KeyError, ValueError): - # surprising: msal.ClientApplication always requests an id token, whose shape shouldn't change - return None + six.raise_from(auth_error, ex) class InteractiveCredential(MsalCredential): diff --git a/sdk/identity/azure-identity/tests/certificate.pem b/sdk/identity/azure-identity/tests/certificate.pem index 4b66bfa021a0..08761c05f2a0 100644 --- a/sdk/identity/azure-identity/tests/certificate.pem +++ b/sdk/identity/azure-identity/tests/certificate.pem @@ -1,49 +1,81 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDL1hG+JYCfIPp3 -tlZ05J4pYIJ3Ckfs432bE3rYuWlR2w9KqdjWkKxuAxpjJ+T+uoqVaT3BFMfi4ZRY -OCI69s4+lP3DwR8uBCp9xyVkF8thXfS3iui0liGDviVBoBJJWvjDFU8a/Hseg+Qf -oxAb6tx0kEc7V3ozBLWoIDJjfwJ3NdsLZGVtAC34qCWeEIvS97CDA4g3Kc6hYJIr -Aa7pxHzo/Nd0U3e7z+DlBcJV7dY6TZUyjBVTpzppWe+XQEOfKsjkDNykHEC1C1bC -lG0u7unS7QOBMd6bOGkeL+Bc+n22slTzs5amsbDLNuobSaUsFt9vgD5jRD6FwhpX -wj/Ek0F7AgMBAAECggEAblU3UWdXUcs2CCqIbcl52wfEVs8X05/n01MeAcWKvqYG -hvGcz7eLvhir5dQoXcF3VhybMrIe6C4WcBIiZSxGwxU+rwEP8YaLwX1UPfOrQM7s -sZTdFTLWfUslO3p7q300fdRA92iG9COMDZvkElh0cBvQksxs9sSr149l9vk+ymtC -uBhZtHG6Ki0BIMBNC9jGUqDuOatXl/dkK4tNjXrNJT7tVwzPaqnNALIWl6B+k9oQ -m1oNhSH2rvs9tw2ITXfIoIk9KdOMjQVUD43wKOaz0hNZhUsb1OFuls7UtRzaFcZH -rMd/M8DtA104QTTlHK+XS7r+nqdv7+ZyB+suTdM+oQKBgQDxCrJZU3hJ0eJ4VYhK -xGDfVGNpYxNkQ4CDB9fwRNbFr/Ck3kgzfE9QxTx1pJOolVmfuFmk9B86in4UNy91 -KdaqT79AU5RdOBXNN6tuMbLC0AVqe8sZq+1vWVVwbCstffxEMmyW1Ju/FLYPl2Zp -e5P96dBh5B3mXrQtpDJ0RkxxaQKBgQDYfE6tQQnQSs2ewD6ae8Mu6j8ueDlVoZ37 -vze1QdBasR26xu2H8XBt3u41zc524BwQsB1GE1tnC8ZylrqwVEayK4FesSQRCO6o -yK8QSdb06I5J4TaN+TppCDPLzstOh0Dmxp+iFUGoErb7AEOLAJ/VebhF9kBZObL/ -HYy4Es+bQwKBgHW/4vYuB3IQXNCp/+V+X1BZ+iJOaves3gekekF+b2itFSKFD8JO -9LQhVfKmTheptdmHhgtF0keXxhV8C+vxX1Ndl7EF41FSh5vzmQRAtPHkCvFEviex -TFD70/gSb1lO1UA/Xbqk69yBcprVPAtFejss0EYx2MVj+CLftmIEwW0ZAoGBAIMG -EVQ45eikLXjkn78+Iq7VZbIJX6IdNBH29I+GqsUJJ5Yw6fh6P3KwF3qG+mvmTfYn -sUAFXS+r58rYwVsRVsxlGmKmUc7hmhibhaEVH72QtvWuEiexbRG+viKfIVuA7t39 -3wXpWZiQ4yBdU4Pgt9wrVEU7ukyGaHiReOa7s90jAoGAJc0K7smn98YutQQ+g2ur -ybfnsl0YdsksaP2S2zvZUmNevKPrgnaIDDabOlhYYga+AK1G3FQ7/nefUgiIg1Nd -kr+T6Q4osS3xHB6Az9p/jaF4R2KaWN2nNVCn7ecsmPxDdM7k1vLxaT26vwO9OP5f -YU/5CeIzrfA5nQyPZkOXZBk= ------END PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAunkGHWyBYbIp6G97dwFeMhB/7c/y1SPlABi6cUJ6hp7gFeRm +Nwl4gDvBmY8e8t6ANQxn3vv3HOp/QZmFl7Cr8aSjvD0JAT2CBbQ/O/Lgzb+5FaGR +vBFbBJ4AcXeHnzJ4ilsCrTJXtIWfo497uAHePQ7F3AtC9vLlf3kOoc7EIkdJ00Cf ++EKjTbU4UhgBUq+zqPMc8QTUyYXvgb8AxPCTJAktL9tiVpsthmK0SsOEZUiscL/U +Ga/N4EonCklD1AAgWHye0bl0kDhzjJSHAuKBrQ6zLIRs6+9OB6Pg4gcmH+Rup5H2 +dSO09N/YBCiiJZTSlqockB3oym2t5z9et2SiNwIDAQABAoIBAQCKzivPG0X0AztO +2i19mHcVrVKNI44POnjsaXvfcyzhqMIFic7MiTA5xEGInRDcmOO2mVV4lvaLf8La +gfz/vXNAnN2E8aoSUkbHGDU52sGcZmrPv0VMSV8HQNXzoJZD2r3/v19urVq79fuv +NM9TWZCkwqpl8bwXNxe+m85YhCFboY9G543qmuXzKAQLoSupT0e4eIo2IGp7eJYK +5J/wtlEumUdhsKo1ajLojDgsgPKfrCyvsmO+bj1dRKGXVLO2SL2pFVCjjHF4SP3q +1WX39beu61Zu+kGthDgj5muHgH06FtnWoHLIUrRmYpM+ezCxQHdRWz7AYjheeE7q +QqJv1PqBAoGBAOlb/gzsps+rInE+LQoEzVj8osILI4NxIpNc6+iG81dEi+zQABX/ +bHV6hXGGceozVcX4B+V7f08PlZIAgM3IDqfy0fH2pwEQahJ8a3MwzCgR66RxYlkX +E8czkoz0pcHW58FnLLlWXpHRALTtqoPP5LnWs0SmoNvcHZ9yjJ6tvpRlAoGBAMyQ +fytsyla1ujO0l/kuLFG7gndeOc96SutH3V17lZ1pN0efHyk2aglOnl6YsdPKLZvZ +3ghj01HV0Q0f//xpftduuA7gdgDzSG1irXsxEidfVxX7RsPxX6cx8dhYnuk5rz5E +XyTko7zTpr+A4XMnq6+JNSSCIE+CVYcYf/hyemxrAoGAeC9py4xCaWgxR/OGzMcm +X3NV++wysSqebRkJYuvF/icOjbuen7W6TVL50Ts2BjHENj6FCpqtObHEDbr2m4Uy +jysPF7g50OF8T+MGkAAM1YJNQ5cl2M564DhefPwvNoMRP1l8/kNOV3k2DPjuvg5f +NZsvHudWp4VZOFqNs9e19MUCgYAjewCDoKfrqDN2mmEtmAOZ3YMAfzhZsyVhb6KG +f1Pw7HnpE0FNXaHAoYE4eRWG3W9Rs9Ud8WqKrCJJO36j4gxdA1grRGVTPt8WEeJz +FozGhXPOXTnl7GyhzDjdRGmznAy4KRWziXCY5MDsQEdaOMw/cvXjsio2gC2jc+1m +QzzWpwKBgHzszJ5s6vcWElox4Yc1elQ8xniPpo3RtfXZOLX8xA4eR9yQawah1zd6 +ChfeYbHVfq007s+RWGTb+KYQ6ic9nkW464qmVxHGBatUo9+MR4Gk8blANoAfHxdV +g6JNgT2kIGu9IEwoD6XQldC/v24bvFSesyGRHNdI4mUG+hhU4aNw +-----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIDazCCAlOgAwIBAgIUF2VIP4+AnEtb52KTCHbo4+fESfswDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTEwMzAyMjQ2MjBaFw0yMjA4 -MTkyMjQ2MjBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDL1hG+JYCfIPp3tlZ05J4pYIJ3Ckfs432bE3rYuWlR -2w9KqdjWkKxuAxpjJ+T+uoqVaT3BFMfi4ZRYOCI69s4+lP3DwR8uBCp9xyVkF8th -XfS3iui0liGDviVBoBJJWvjDFU8a/Hseg+QfoxAb6tx0kEc7V3ozBLWoIDJjfwJ3 -NdsLZGVtAC34qCWeEIvS97CDA4g3Kc6hYJIrAa7pxHzo/Nd0U3e7z+DlBcJV7dY6 -TZUyjBVTpzppWe+XQEOfKsjkDNykHEC1C1bClG0u7unS7QOBMd6bOGkeL+Bc+n22 -slTzs5amsbDLNuobSaUsFt9vgD5jRD6FwhpXwj/Ek0F7AgMBAAGjUzBRMB0GA1Ud -DgQWBBT6Mf9uXFB67bY2PeW3GCTKfkO7vDAfBgNVHSMEGDAWgBT6Mf9uXFB67bY2 -PeW3GCTKfkO7vDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCZ -1+kTISX85v9/ag7glavaPFUYsOSOOofl8gSzov7L01YL+srq7tXdvZmWrjQ/dnOY -h18rp9rb24vwIYxNioNG/M2cW1jBJwEGsDPOwdPV1VPcRmmUJW9kY130gRHBCd/N -qB7dIkcQnpNsxPIIWI+sRQp73U0ijhOByDnCNHLHon6vbfFTwkO1XggmV5BdZ3uQ -JNJyckILyNzlhmf6zhonMp4lVzkgxWsAm2vgdawd6dmBa+7Avb2QK9s+IdUSutFh -DgW2L12Obgh12Y4sf1iKQXA0RbZ2k+XQIz8EKZa7vJQY0ciYXSgB/BV3a96xX3cx -LIPL8Vam8Ytkopi3gsGA ------END CERTIFICATE----- \ No newline at end of file +MIID7zCCAdcCAQEwDQYJKoZIhvcNAQEFBQAwPjELMAkGA1UEBhMCVVMxDDAKBgNV +BAoMA3h5ejEMMAoGA1UECwwDYWJjMRMwEQYDVQQDDApJTlRFUklNLUNOMCAXDTIw +MDgyMTE3MTA0M1oYDzMzODkwODA0MTcxMDQzWjA7MQswCQYDVQQGEwJVUzEMMAoG +A1UECgwDeHl6MQwwCgYDVQQLDANhYmMxEDAOBgNVBAMMB1VTRVItQ04wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6eQYdbIFhsinob3t3AV4yEH/tz/LV +I+UAGLpxQnqGnuAV5GY3CXiAO8GZjx7y3oA1DGfe+/cc6n9BmYWXsKvxpKO8PQkB +PYIFtD878uDNv7kVoZG8EVsEngBxd4efMniKWwKtMle0hZ+jj3u4Ad49DsXcC0L2 +8uV/eQ6hzsQiR0nTQJ/4QqNNtThSGAFSr7Oo8xzxBNTJhe+BvwDE8JMkCS0v22JW +my2GYrRKw4RlSKxwv9QZr83gSicKSUPUACBYfJ7RuXSQOHOMlIcC4oGtDrMshGzr +704Ho+DiByYf5G6nkfZ1I7T039gEKKIllNKWqhyQHejKba3nP163ZKI3AgMBAAEw +DQYJKoZIhvcNAQEFBQADggIBADfitSfjlYa2inBKlpWN8VT0DPm5uw8EHuwLymCM +WYrQMCuQVE2xYoqCSmXj6KLFt8ycgxHsthdkAzXxDhawaKjz2UFp6nszmUA4xfvS +mxLSajwzK/KMBkjdFL7TM+TTBJ1bleDbmoJvDiUeQwisbb1Uh8b3v/jpBwoiamm8 +Y4Ca5A15SeBUvAt0/Mc4XJfZ/Ts+LBAPevI9ZyU7C5JZky1q41KPklEHfFZKQRfP +cTyTYYvlPoq57C8XPDs6r50EV3B6Z8MN21OB6MVGi8BOY/c7a2h1ZOhxNyBnJuQX +w4meJthoKcHUnAs8YCrEoQKayMqPH0Vdhaii/gx4jAgh4PNyIZz5cAst+ybPtQj4 +i7LFEWjxis+NLQMHhyE4fIGIkEjzU0uGDugifheIwKALqYEgMDrcoolwvGMdPxGo +Qps7tkad5vZV9d9+tTbI+DMB16Y51S04/u1dGFz3jSrDVF08PznJc99VB69OReiC +K17n8Xyox/VAaYsRFbOAJpLRWwcnotDpFQbgiLrmXxNOoiWPNbQsQzaQx7cR9okQ +v5RTpFAkrdjadhMsXFFiQh+axlaGD368ZGAj5ZoyOiXkV88tNCtyP/RDgW5ftQQ7 +fdv05bNXhDfLgEgQvVSDfClDL1hKukLmLQS3ILfB4FlM/XmE+FW/qgo9aSx2XIbx +E4ie +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFGTCCAwGgAwIBAgIUBpOlpNN/cgasvozVw6mfa04+ZC0wDQYJKoZIhvcNAQEL +BQAwOzELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA3h6eTEMMAoGA1UECwwDYWJjMRAw +DgYDVQQDDAdST09ULUNOMCAXDTIwMDgyMTE3MTAyNVoYDzMzODkwODA0MTcxMDI1 +WjA+MQswCQYDVQQGEwJVUzEMMAoGA1UECgwDeHl6MQwwCgYDVQQLDANhYmMxEzAR +BgNVBAMMCklOVEVSSU0tQ04wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCr+Tblr4DhX3Xahbei00OJnUgRw6FMsnyROZ170Lx0YNcOrRJ9PuaOZiYXY2Hm +t71o/PZjMtmiYMIxFaiMnql/dCca777l+uBmlwFOR8bquBWiLStmPpvf7Kh5GZNw +XvLGAhk/oxG0O9Pa3OfrlD5vrn/UEGJBu0C+c6ZSLyRk8RjAh8ZbUvnDhhQw3PoK +MQSmFK8BN8X34elu7kq0j7nS0D6Mt7eS40oYeHEaQDdBGl8f7rcqC3RjJ/b/F9wA ++CsKaps6TvpxE7ln9Y3+0yscgeRbyHW0zem6U7MMvVnK/znuNY90Wmajbea7SUj6 +nGZpLGS1TqS4H5rn9U1N1WCSyFukTpAQLCPQHeUrSiHKa9Ye5KuC6u2ZXgy0qpGj +nMLu+7746wemi7jN06yZjEmDVneMNCxjLYs4ZhuhiTEItlZpR0VBugNbKo2mJw2U +UesizB3AzQkqGOKp70y74yC+ykLkR5vRNyY3MENJ+W83U1haS7C1rhqFV4eXflVe +EHl8tj7p4KrfhSPr0Rd12UIWDXkYUpCAPlDMdEa9+SDAyuSnkN4P1fAeuzG01jeJ +bnsrWgs3gH3KaGBcPTV4tOTavilGNYDvHZbN9XpYZoZQoPrDZc61M5Ol/cxBahkO +n4aDyhpx5hHnSs7VQuHnjeMUxt3J5HqrXPvaf6uPYNT8KQIDAQABoxAwDjAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCHCxFqJwfVMI9kMvwlj+sxd4Q5 +KuyWxlXRfzpYZ/6JCUq7VBceRVJ87KytMNCyq61rd3Jhb8ssoMCENB68HYhIFUGz +GR92AAc6LTh2Y3vQAg640Cz2vLCGnqnlbIslYV6fzxYqgSopR5wJ4D/kJ9w7NSrC +paN6bS8Olv//tN6RSnvEMJZdXFA40xFin6qT8Op3nrysEE7Z84wPG9Wj2DXskX6v +bZenCEgl1/Ezif5IEgJcYdRkXtYPp6JNbVV+KjDTIMEaUVMpGMGefrt22E+4nSa3 +qFvcbzYEKeANe9IAxdPzeWiQ2U90PqWFYCA9sOVsrlSwrup+yYXl0yhTxKY67NCX +gyVtZRnzawv0AVFsfCOT4V0wJSuUz4BV6sH7kl2C7FW3zqYVdFEDigbUNsEEh/jF +3JiAtgNbpJ8TtiCFrCI4g9Jepa3polVPzDD8mLtkWWnfSBN/28cxa2jiUlfQxB39 +kyqu4rWbm01lyucJxVgJzH0SGyEM5OvF/OIOU3Q7UIXEcZSX3m4Xo59+v6ZNDwKL +PcFDNK+PL3WNYfdexQCSAbLm1gkUrVIqvidpCSSVv5oWwTM5m7rbA16Hlu4Ea2ep +Pl7I9YXXXnIEFqLYZDnCJglcXmlt6OjI8D3w0TRWHb6bFqubDP417sJDX1S6udN5 +wOnOIqg0ZZcqfvpxXA== +-----END CERTIFICATE----- diff --git a/sdk/identity/azure-identity/tests/helpers.py b/sdk/identity/azure-identity/tests/helpers.py index 692c686df232..1bac8aaceedd 100644 --- a/sdk/identity/azure-identity/tests/helpers.py +++ b/sdk/identity/azure-identity/tests/helpers.py @@ -14,35 +14,33 @@ import mock # type: ignore -# build_* lifted from msal tests def build_id_token( iss="issuer", sub="subject", - aud="my_client_id", + aud="client-id", username="username", - tenant_id="tenant id", - object_id="object id", - exp=None, - iat=None, + tenant_id="tenant-id", + object_id="object-id", **claims -): # AAD issues "preferred_username", ADFS issues "upn" - return "header.%s.signature" % base64.b64encode( - json.dumps( - dict( - { - "iss": iss, - "sub": sub, - "aud": aud, - "exp": exp or (time.time() + 100), - "iat": iat or time.time(), - "tid": tenant_id, - "oid": object_id, - "preferred_username": username, - }, - **claims - ) - ).encode() - ).decode("utf-8") +): + token_claims = id_token_claims( + iss=iss, sub=sub, aud=aud, tid=tenant_id, oid=object_id, preferred_username=username, **claims + ) + jwt_payload = base64.b64encode(json.dumps(token_claims).encode()).decode("utf-8") + return "header.{}.signature".format(jwt_payload) + + +def build_adfs_id_token(iss="issuer", sub="subject", aud="client-id", username="username", **claims): + token_claims = id_token_claims(iss=iss, sub=sub, aud=aud, upn=username, **claims) + jwt_payload = base64.b64encode(json.dumps(token_claims).encode()).decode("utf-8") + return "header.{}.signature".format(jwt_payload) + + +def id_token_claims(iss, sub, aud, exp=None, iat=None, **claims): + return dict( + {"iss": iss, "sub": sub, "aud": aud, "exp": exp or int(time.time()) + 3600, "iat": iat or int(time.time())}, + **claims + ) def build_aad_response( # simulate a response from AAD diff --git a/sdk/identity/azure-identity/tests/test_browser_credential.py b/sdk/identity/azure-identity/tests/test_browser_credential.py index e07c69f30396..25ea77f71b66 100644 --- a/sdk/identity/azure-identity/tests/test_browser_credential.py +++ b/sdk/identity/azure-identity/tests/test_browser_credential.py @@ -111,10 +111,13 @@ def test_disable_automatic_authentication(): @patch("azure.identity._credentials.browser.webbrowser.open", lambda _: True) def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) - + client_id = "client-id" transport = validating_transport( requests=[Request()] * 2, - responses=[get_discovery_response(), mock_response(json_payload=build_aad_response(access_token="**"))], + responses=[ + get_discovery_response(), + mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token(aud=client_id))), + ], ) # mock local server fakes successful authentication by immediately returning a well-formed response @@ -123,7 +126,7 @@ def test_policies_configurable(): server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response)) credential = InteractiveBrowserCredential( - policies=[policy], transport=transport, server_class=server_class, _cache=TokenCache() + policies=[policy], client_id=client_id, transport=transport, server_class=server_class, _cache=TokenCache() ) with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state): @@ -134,9 +137,13 @@ def test_policies_configurable(): @patch("azure.identity._credentials.browser.webbrowser.open", lambda _: True) def test_user_agent(): + client_id = "client-id" transport = validating_transport( requests=[Request(), Request(required_headers={"User-Agent": USER_AGENT})], - responses=[get_discovery_response(), mock_response(json_payload=build_aad_response(access_token="**"))], + responses=[ + get_discovery_response(), + mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token(aud=client_id))), + ], ) # mock local server fakes successful authentication by immediately returning a well-formed response @@ -144,7 +151,9 @@ def test_user_agent(): auth_code_response = {"code": "authorization-code", "state": [oauth_state]} server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response)) - credential = InteractiveBrowserCredential(transport=transport, server_class=server_class, _cache=TokenCache()) + credential = InteractiveBrowserCredential( + client_id=client_id, transport=transport, server_class=server_class, _cache=TokenCache() + ) with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state): credential.get_token("scope") @@ -284,7 +293,7 @@ def test_redirect_server(): thread.start() # send a request, verify the server exposes the query - url = "http://127.0.0.1:{}/?{}={}".format(port, expected_param, expected_value) # nosec + url = "http://127.0.0.1:{}/?{}={}".format(port, expected_param, expected_value) # nosec response = urllib.request.urlopen(url) # nosec assert response.code == 200 diff --git a/sdk/identity/azure-identity/tests/test_certificate_credential.py b/sdk/identity/azure-identity/tests/test_certificate_credential.py index 5f4b49f416e2..7765a9e3e548 100644 --- a/sdk/identity/azure-identity/tests/test_certificate_credential.py +++ b/sdk/identity/azure-identity/tests/test_certificate_credential.py @@ -109,7 +109,8 @@ def test_authority(authority): @pytest.mark.parametrize("cert_path,cert_password", BOTH_CERTS) -def test_request_body(cert_path, cert_password): +@pytest.mark.parametrize("send_certificate", (True, False)) +def test_request_body(cert_path, cert_password, send_certificate): access_token = "***" authority = "authority.com" client_id = "client-id" @@ -124,18 +125,24 @@ def mock_send(request, **kwargs): assert request.body["scope"] == expected_scope with open(cert_path, "rb") as cert_file: - validate_jwt(request, client_id, cert_file.read()) + validate_jwt(request, client_id, cert_file.read(), expect_x5c=send_certificate) - return mock_response(json_payload={"token_type": "Bearer", "expires_in": 42, "access_token": access_token}) + return mock_response(json_payload=build_aad_response(access_token=access_token)) cred = CertificateCredential( - tenant_id, client_id, cert_path, password=cert_password, transport=Mock(send=mock_send), authority=authority + tenant_id, + client_id, + cert_path, + password=cert_password, + transport=Mock(send=mock_send), + authority=authority, + send_certificate=send_certificate, ) token = cred.get_token(expected_scope) assert token.token == access_token -def validate_jwt(request, client_id, pem_bytes): +def validate_jwt(request, client_id, pem_bytes, expect_x5c=False): """Validate the request meets AAD's expectations for a client credential grant using a certificate, as documented at https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials """ @@ -146,16 +153,28 @@ def validate_jwt(request, client_id, pem_bytes): jwt = six.ensure_str(request.body["client_assertion"]) header, payload, signature = (urlsafeb64_decode(s) for s in jwt.split(".")) signed_part = jwt[: jwt.rfind(".")] + claims = json.loads(payload.decode("utf-8")) + assert claims["aud"] == request.url + assert claims["iss"] == claims["sub"] == client_id deserialized_header = json.loads(header.decode("utf-8")) assert deserialized_header["alg"] == "RS256" assert deserialized_header["typ"] == "JWT" + if expect_x5c: + # x5c should have all the certs in the PEM file, in order, minus headers and footers + pem_lines = pem_bytes.decode("utf-8").splitlines() + header = "-----BEGIN CERTIFICATE-----" + assert len(deserialized_header["x5c"]) == pem_lines.count(header) + + # concatenate the PEM file's certs, removing headers and footers + chain_start = pem_lines.index(header) + pem_chain_content = "".join(line for line in pem_lines[chain_start:] if not line.startswith("-" * 5)) + assert "".join(deserialized_header["x5c"]) == pem_chain_content, "JWT's x5c claim contains unexpected content" + else: + assert "x5c" not in deserialized_header assert urlsafeb64_decode(deserialized_header["x5t"]) == cert.fingerprint(hashes.SHA1()) # nosec - assert claims["aud"] == request.url - assert claims["iss"] == claims["sub"] == client_id - cert.public_key().verify(signature, signed_part.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256()) diff --git a/sdk/identity/azure-identity/tests/test_device_code_credential.py b/sdk/identity/azure-identity/tests/test_device_code_credential.py index f33553dcd6e3..6428f7035345 100644 --- a/sdk/identity/azure-identity/tests/test_device_code_credential.py +++ b/sdk/identity/azure-identity/tests/test_device_code_credential.py @@ -93,7 +93,9 @@ def test_disable_automatic_authentication(): empty_cache = TokenCache() # empty cache makes silent auth impossible transport = Mock(send=Mock(side_effect=Exception("no request should be sent"))) - credential = DeviceCodeCredential("client-id", disable_automatic_authentication=True, transport=transport, _cache=empty_cache) + credential = DeviceCodeCredential( + "client-id", disable_automatic_authentication=True, transport=transport, _cache=empty_cache + ) with pytest.raises(AuthenticationRequiredError): credential.get_token("scope") @@ -102,6 +104,7 @@ def test_disable_automatic_authentication(): def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) + client_id = "client-id" transport = validating_transport( requests=[Request()] * 3, responses=[ @@ -115,12 +118,16 @@ def test_policies_configurable(): "expires_in": 42, } ), - mock_response(json_payload=dict(build_aad_response(access_token="**"), scope="scope")), + mock_response( + json_payload=dict( + build_aad_response(access_token="**", id_token=build_id_token(aud=client_id)), scope="scope" + ) + ), ], ) credential = DeviceCodeCredential( - client_id="client-id", prompt_callback=Mock(), policies=[policy], transport=transport, _cache=TokenCache() + client_id=client_id, prompt_callback=Mock(), policies=[policy], transport=transport, _cache=TokenCache() ) credential.get_token("scope") @@ -129,6 +136,7 @@ def test_policies_configurable(): def test_user_agent(): + client_id = "client-id" transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], responses=[ @@ -141,18 +149,23 @@ def test_user_agent(): "expires_in": 42, } ), - mock_response(json_payload=dict(build_aad_response(access_token="**"), scope="scope")), + mock_response( + json_payload=dict( + build_aad_response(access_token="**", id_token=build_id_token(aud=client_id)), scope="scope" + ) + ), ], ) credential = DeviceCodeCredential( - client_id="client-id", prompt_callback=Mock(), transport=transport, _cache=TokenCache() + client_id=client_id, prompt_callback=Mock(), transport=transport, _cache=TokenCache() ) credential.get_token("scope") def test_device_code_credential(): + client_id = "client-id" expected_token = "access-token" user_code = "user-code" verification_uri = "verification-uri" @@ -172,20 +185,26 @@ def test_device_code_credential(): } ), mock_response( - json_payload={ - "access_token": expected_token, - "expires_in": expires_in, - "scope": "scope", - "token_type": "Bearer", - "refresh_token": "_", - } + json_payload=dict( + build_aad_response( + access_token=expected_token, + expires_in=expires_in, + refresh_token="_", + id_token=build_id_token(aud=client_id), + ), + scope="scope", + ), ), ], ) callback = Mock() credential = DeviceCodeCredential( - client_id="_", prompt_callback=callback, transport=transport, instance_discovery=False, _cache=TokenCache() + client_id=client_id, + prompt_callback=callback, + transport=transport, + instance_discovery=False, + _cache=TokenCache(), ) now = datetime.datetime.utcnow() diff --git a/sdk/identity/azure-identity/tests/test_interactive_credential.py b/sdk/identity/azure-identity/tests/test_interactive_credential.py index 645e74f21bd0..a708b7c2c9fc 100644 --- a/sdk/identity/azure-identity/tests/test_interactive_credential.py +++ b/sdk/identity/azure-identity/tests/test_interactive_credential.py @@ -18,7 +18,21 @@ except ImportError: # python < 3.3 from mock import Mock, patch # type: ignore -from helpers import build_aad_response +from helpers import build_aad_response, build_id_token, id_token_claims + + +# fake object for tests which need to exercise request_token but don't care about its return value +REQUEST_TOKEN_RESULT = build_aad_response( + access_token="***", + id_token_claims=id_token_claims( + aud="...", + iss="http://localhost/tenant", + sub="subject", + preferred_username="...", + tenant_id="...", + object_id="...", + ), +) class MockCredential(InteractiveCredential): @@ -132,7 +146,7 @@ def test_scopes_round_trip(): def validate_scopes(*scopes, **_): assert scopes == (scope,) - return {"access_token": "**", "expires_in": 42} + return REQUEST_TOKEN_RESULT request_token = Mock(wraps=validate_scopes) credential = MockCredential(disable_automatic_authentication=True, request_token=request_token) @@ -158,7 +172,7 @@ def test_authenticate_default_scopes(authority, expected_scope): def validate_scopes(*scopes): assert scopes == (expected_scope,) - return {"access_token": "**", "expires_in": 42} + return REQUEST_TOKEN_RESULT request_token = Mock(wraps=validate_scopes) MockCredential(authority=authority, request_token=request_token).authenticate() @@ -176,7 +190,7 @@ def test_authenticate_unknown_cloud(): def test_authenticate_ignores_disable_automatic_authentication(option): """authenticate should prompt for authentication regardless of the credential's configuration""" - request_token = Mock(return_value={"access_token": "**", "expires_in": 42}) + request_token = Mock(return_value=REQUEST_TOKEN_RESULT) MockCredential(request_token=request_token, disable_automatic_authentication=option).authenticate() assert request_token.call_count == 1, "credential didn't begin interactive authentication" @@ -296,19 +310,22 @@ def _request_token(self, *_, **__): assert record.home_account_id == "{}.{}".format(object_id, home_tenant) -def test_home_account_id_no_client_info(): - """the credential should use the subject claim as home_account_id when MSAL doesn't provide client_info""" +def test_adfs(): + """the credential should be able to construct an AuthenticationRecord from an ADFS response returned by MSAL""" + authority = "localhost" subject = "subject" + tenant = "adfs" + username = "username" msal_response = build_aad_response(access_token="***", refresh_token="**") - msal_response["id_token_claims"] = { - "aud": "client-id", - "iss": "https://localhost", - "object_id": "some-guid", - "tid": "some-tenant", - "preferred_username": "me", - "sub": subject, - } + msal_response["id_token_claims"] = id_token_claims( + aud="client-id", + iss="https://{}/{}".format(authority, tenant), + sub=subject, + tenant_id=tenant, + object_id="object-id", + upn=username, + ) class TestCredential(InteractiveCredential): def __init__(self, **kwargs): @@ -318,4 +335,7 @@ def _request_token(self, *_, **__): return msal_response record = TestCredential().authenticate() + assert record.authority == authority assert record.home_account_id == subject + assert record.tenant_id == tenant + assert record.username == username diff --git a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py index efb5bcc668af..5d756ecface1 100644 --- a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py +++ b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py @@ -769,7 +769,7 @@ def get_account_event( uid=uid, utid=utid, refresh_token=refresh_token, - id_token=build_id_token(aud=client_id, preferred_username=username), + id_token=build_id_token(aud=client_id, username=username), foci="1", **kwargs ), diff --git a/sdk/identity/azure-identity/tests/test_username_password_credential.py b/sdk/identity/azure-identity/tests/test_username_password_credential.py index f82d251090b0..5e4349a6e6df 100644 --- a/sdk/identity/azure-identity/tests/test_username_password_credential.py +++ b/sdk/identity/azure-identity/tests/test_username_password_credential.py @@ -35,7 +35,8 @@ def test_policies_configurable(): transport = validating_transport( requests=[Request()] * 3, - responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**"))], + responses=[get_discovery_response()] * 2 + + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", policies=[policy], transport=transport) @@ -47,7 +48,8 @@ def test_policies_configurable(): def test_user_agent(): transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], - responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**"))], + responses=[get_discovery_response()] * 2 + + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) @@ -57,6 +59,7 @@ def test_user_agent(): def test_username_password_credential(): expected_token = "access-token" + client_id = "client-id" transport = validating_transport( requests=[Request()] * 3, # not validating requests because they're formed by MSAL responses=[ @@ -66,18 +69,13 @@ def test_username_password_credential(): mock_response(json_payload={}), # token request mock_response( - json_payload={ - "access_token": expected_token, - "expires_in": 42, - "token_type": "Bearer", - "ext_expires_in": 42, - } + json_payload=build_aad_response(access_token=expected_token, id_token=build_id_token(aud=client_id)) ), ], ) credential = UsernamePasswordCredential( - client_id="some-guid", + client_id=client_id, username="user@azure", password="secret_password", transport=transport, diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/__init__.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/__init__.py index 679ab6995134..008faf70ac0d 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/__init__.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/__init__.py @@ -2,4 +2,15 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore +from ._access_control_client import KeyVaultAccessControlClient +from ._internal.client_base import ApiVersion +from ._models import KeyVaultPermission, KeyVaultRoleAssignment, KeyVaultRoleDefinition + + +__all__ = [ + "ApiVersion", + "KeyVaultAccessControlClient", + "KeyVaultPermission", + "KeyVaultRoleAssignment", + "KeyVaultRoleDefinition", +] diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_access_control_client.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_access_control_client.py new file mode 100644 index 000000000000..862fc8cea88c --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_access_control_client.py @@ -0,0 +1,114 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from typing import TYPE_CHECKING + +from azure.core.tracing.decorator import distributed_trace + +from ._models import KeyVaultRoleAssignment, KeyVaultRoleDefinition +from ._internal import KeyVaultClientBase + +if TYPE_CHECKING: + from typing import Any, Union + from uuid import UUID + from azure.core.paging import ItemPaged + + +class KeyVaultAccessControlClient(KeyVaultClientBase): + """Manages role-based access to Azure Key Vault. + + :param str vault_url: URL of the vault the client will manage. This is also called the vault's "DNS Name". + :param credential: an object which can provide an access token for the vault, such as a credential from + :mod:`azure.identity` + """ + + # pylint:disable=protected-access + + @distributed_trace + def create_role_assignment(self, role_scope, role_assignment_name, role_definition_id, principal_id, **kwargs): + # type: (str, Union[str, UUID], str, str, **Any) -> KeyVaultRoleAssignment + """Create a role assignment. + + :param str role_scope: scope the role assignment will apply over + :param role_assignment_name: a name for the role assignment. Must be a UUID. + :type role_assignment_name: str or uuid.UUID + :param str role_definition_id: ID of the role's definition + :param str principal_id: Azure Active Directory object ID of the principal which will be assigned the role. The + principal can be a user, service principal, or security group. + :rtype: KeyVaultRoleAssignment + """ + create_parameters = self._client.role_assignments.models.RoleAssignmentCreateParameters( + properties=self._client.role_assignments.models.RoleAssignmentProperties( + principal_id=principal_id, role_definition_id=str(role_definition_id) + ) + ) + assignment = self._client.role_assignments.create( + vault_base_url=self._vault_url, + scope=role_scope, + role_assignment_name=role_assignment_name, + parameters=create_parameters, + **kwargs + ) + return KeyVaultRoleAssignment._from_generated(assignment) + + @distributed_trace + def delete_role_assignment(self, role_scope, role_assignment_name, **kwargs): + # type: (str, Union[str, UUID], **Any) -> KeyVaultRoleAssignment + """Delete a role assignment. + + :param str role_scope: the assignment's scope, for example "/", "/keys", or "/keys/" + :param role_assignment_name: the assignment's name. Must be a UUID. + :type role_assignment_name: str or uuid.UUID + :returns: the deleted assignment + :rtype: KeyVaultRoleAssignment + """ + assignment = self._client.role_assignments.delete( + vault_base_url=self._vault_url, scope=role_scope, role_assignment_name=str(role_assignment_name), **kwargs + ) + return KeyVaultRoleAssignment._from_generated(assignment) + + @distributed_trace + def get_role_assignment(self, role_scope, role_assignment_name, **kwargs): + # type: (str, Union[str, UUID], **Any) -> KeyVaultRoleAssignment + """Get a role assignment. + + :param str role_scope: the assignment's scope, for example "/", "/keys", or "/keys/" + :param role_assignment_name: the assignment's name. Must be a UUID. + :type role_assignment_name: str or uuid.UUID + :rtype: KeyVaultRoleAssignment + """ + assignment = self._client.role_assignments.get( + vault_base_url=self._vault_url, scope=role_scope, role_assignment_name=str(role_assignment_name), **kwargs + ) + return KeyVaultRoleAssignment._from_generated(assignment) + + @distributed_trace + def list_role_assignments(self, role_scope, **kwargs): + # type: (str, **Any) -> ItemPaged[KeyVaultRoleAssignment] + """List all role assignments for a scope. + + :param str role_scope: scope of the role assignments + :rtype: ~azure.core.paging.ItemPaged[KeyVaultRoleAssignment] + """ + return self._client.role_assignments.list_for_scope( + self._vault_url, + role_scope, + cls=lambda result: [KeyVaultRoleAssignment._from_generated(a) for a in result], + **kwargs + ) + + @distributed_trace + def list_role_definitions(self, role_scope, **kwargs): + # type: (str, **Any) -> ItemPaged[KeyVaultRoleDefinition] + """List all role definitions applicable at and above a scope. + + :param str role_scope: scope of the role definitions + :rtype: ~azure.core.paging.ItemPaged[KeyVaultRoleDefinition] + """ + return self._client.role_definitions.list( + self._vault_url, + role_scope, + cls=lambda result: [KeyVaultRoleDefinition._from_generated(d) for d in result], + **kwargs + ) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_models.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_models.py new file mode 100644 index 000000000000..81d9b1165a3e --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_models.py @@ -0,0 +1,167 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + + +# pylint:disable=protected-access + + +class KeyVaultPermission(object): + """Role definition permissions. + + :ivar list[str] actions: allowed actions + :ivar list[str] not_actions: denied actions + :ivar list[str] data_actions: allowed data actions + :ivar list[str] not_data_actions: denied data actions + """ + + def __init__(self, **kwargs): + # type: (**Any) -> None + self.actions = kwargs.get("actions") + self.not_actions = kwargs.get("not_actions") + self.data_actions = kwargs.get("data_actions") + self.not_data_actions = kwargs.get("not_data_actions") + + @classmethod + def _from_generated(cls, permissions): + return cls( + actions=permissions.actions, + not_actions=permissions.not_actions, + data_actions=permissions.data_actions, + not_data_actions=permissions.not_data_actions, + ) + + +class KeyVaultRoleAssignment(object): + """Represents the assignment to a principal of a role over a scope""" + + def __init__(self, **kwargs): + # type: (**Any) -> None + self._assignment_id = kwargs.get("assignment_id") + self._name = kwargs.get("name") + self._properties = kwargs.get("properties") + self._type = kwargs.get("assignment_type") + + def __repr__(self): + # type: () -> str + return "KeyVaultRoleAssignment<{}>".format(self._assignment_id) + + @property + def assignment_id(self): + # type: () -> str + """unique identifier for this assignment""" + return self._assignment_id + + @property + def name(self): + # type: () -> str + """name of the assignment""" + return self._name + + @property + def principal_id(self): + # type: () -> str + """ID of the principal this assignment applies to. + + This maps to the ID inside the Active Directory. It can point to a user, service principal, or security group. + """ + return self._properties.principal_id + + @property + def role_definition_id(self): + # type: () -> str + """ID of the role's definition""" + return self._properties.role_definition_id + + @property + def scope(self): + # type: () -> str + """scope of the assignment""" + return self._properties.scope + + @property + def type(self): + # type: () -> str + """the type of this assignment""" + return self._type + + @classmethod + def _from_generated(cls, role_assignment): + return cls( + assignment_id=role_assignment.id, + name=role_assignment.name, + assignment_type=role_assignment.type, + properties=KeyVaultRoleAssignmentProperties._from_generated(role_assignment.properties), + ) + + +class KeyVaultRoleAssignmentProperties(object): + def __init__(self, **kwargs): + # type: (**Any) -> None + self.principal_id = kwargs.get("principal_id") + self.role_definition_id = kwargs.get("role_definition_id") + self.scope = kwargs.get("scope") + + def __repr__(self): + # type: () -> str + return "KeyVaultRoleAssignmentProperties(principal_id={}, role_definition_id={}, scope={})".format( + self.principal_id, self.role_definition_id, self.scope + )[:1024] + + @classmethod + def _from_generated(cls, role_assignment_properties): + # the generated RoleAssignmentProperties and RoleAssignmentPropertiesWithScope + # models differ only in that the latter has a "scope" attribute + return cls( + principal_id=role_assignment_properties.principal_id, + role_definition_id=role_assignment_properties.role_definition_id, + scope=getattr(role_assignment_properties, "scope", None), + ) + + +class KeyVaultRoleDefinition(object): + """Role definition. + + :ivar str id: The role definition ID. + :ivar str name: The role definition name. + :ivar str type: The role definition type. + :ivar str role_name: The role name. + :ivar str description: The role definition description. + :ivar str role_type: The role type. + :ivar permissions: Role definition permissions. + :vartype permissions: list[KeyVaultPermission] + :ivar list[str] assignable_scopes: Role definition assignable scopes. + """ + + def __init__(self, **kwargs): + # type: (**Any) -> None + self.id = kwargs.get("id") + self.name = kwargs.get("name") + self.role_name = kwargs.get("role_name") + self.description = kwargs.get("description") + self.role_type = kwargs.get("role_type") + self.type = kwargs.get("type") + self.permissions = kwargs.get("permissions") + self.assignable_scopes = kwargs.get("assignable_scopes") + + def __repr__(self): + # type: () -> str + return "".format(self.role_name)[:1024] + + @classmethod + def _from_generated(cls, definition): + return cls( + assignable_scopes=definition.assignable_scopes, + description=definition.description, + id=definition.id, + name=definition.name, + permissions=[KeyVaultPermission._from_generated(p) for p in definition.permissions], + role_name=definition.role_name, + role_type=definition.role_type, + type=definition.type, + ) diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/__init__.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/__init__.py index b74cfa3b899c..45ea36c883e7 100644 --- a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/__init__.py +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/__init__.py @@ -2,3 +2,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ +from ._access_control_client import KeyVaultAccessControlClient + +__all__ = ["KeyVaultAccessControlClient"] diff --git a/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/_access_control_client.py b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/_access_control_client.py new file mode 100644 index 000000000000..a9cd70ffcd66 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/_access_control_client.py @@ -0,0 +1,121 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from typing import TYPE_CHECKING + +from azure.core.tracing.decorator import distributed_trace +from azure.core.tracing.decorator_async import distributed_trace_async + +from .._models import KeyVaultRoleAssignment, KeyVaultRoleDefinition +from .._internal import AsyncKeyVaultClientBase + +if TYPE_CHECKING: + from typing import Any, Union + from uuid import UUID + from azure.core.async_paging import AsyncItemPaged + + +class KeyVaultAccessControlClient(AsyncKeyVaultClientBase): + """Manages role-based access to Azure Key Vault. + + :param str vault_url: URL of the vault the client will manage. This is also called the vault's "DNS Name". + :param credential: an object which can provide an access token for the vault, such as a credential from + :mod:`azure.identity` + """ + + # pylint:disable=protected-access + + @distributed_trace_async + async def create_role_assignment( + self, + role_scope: str, + role_assignment_name: "Union[str, UUID]", + role_definition_id: str, + principal_id: str, + **kwargs: "Any" + ) -> KeyVaultRoleAssignment: + """Create a role assignment. + + :param str role_scope: scope the role assignment will apply over + :param role_assignment_name: a name for the role assignment. Must be a UUID. + :type role_assignment_name: str or uuid.UUID + :param str role_definition_id: ID of the role's definition + :param str principal_id: Azure Active Directory object ID of the principal which will be assigned the role. The + principal can be a user, service principal, or security group. + :rtype: KeyVaultRoleAssignment + """ + create_parameters = self._client.role_assignments.models.RoleAssignmentCreateParameters( + properties=self._client.role_assignments.models.RoleAssignmentProperties( + principal_id=principal_id, role_definition_id=str(role_definition_id) + ) + ) + assignment = await self._client.role_assignments.create( + vault_base_url=self._vault_url, + scope=role_scope, + role_assignment_name=role_assignment_name, + parameters=create_parameters, + **kwargs + ) + return KeyVaultRoleAssignment._from_generated(assignment) + + @distributed_trace_async + async def delete_role_assignment( + self, role_scope: str, role_assignment_name: "Union[str, UUID]", **kwargs: "Any" + ) -> KeyVaultRoleAssignment: + """Delete a role assignment. + + :param str role_scope: the assignment's scope, for example "/", "/keys", or "/keys/" + :param role_assignment_name: the assignment's name. Must be a UUID. + :type role_assignment_name: str or uuid.UUID + :returns: the deleted assignment + :rtype: KeyVaultRoleAssignment + """ + assignment = await self._client.role_assignments.delete( + vault_base_url=self._vault_url, scope=role_scope, role_assignment_name=str(role_assignment_name), **kwargs + ) + return KeyVaultRoleAssignment._from_generated(assignment) + + @distributed_trace_async + async def get_role_assignment( + self, role_scope: str, role_assignment_name: "Union[str, UUID]", **kwargs: "Any" + ) -> KeyVaultRoleAssignment: + """Get a role assignment. + + :param str role_scope: the assignment's scope, for example "/", "/keys", or "/keys/" + :param role_assignment_name: the assignment's name. Must be a UUID. + :type role_assignment_name: str or uuid.UUID + :rtype: KeyVaultRoleAssignment + """ + assignment = await self._client.role_assignments.get( + vault_base_url=self._vault_url, scope=role_scope, role_assignment_name=str(role_assignment_name), **kwargs + ) + return KeyVaultRoleAssignment._from_generated(assignment) + + @distributed_trace + def list_role_assignments(self, role_scope: str, **kwargs: "Any") -> "AsyncItemPaged[KeyVaultRoleAssignment]": + """List all role assignments for a scope. + + :param str role_scope: scope of the role assignments + :rtype: ~azure.core.async_paging.AsyncItemPaged[KeyVaultRoleAssignment] + """ + return self._client.role_assignments.list_for_scope( + self._vault_url, + role_scope, + cls=lambda result: [KeyVaultRoleAssignment._from_generated(a) for a in result], + **kwargs + ) + + @distributed_trace + def list_role_definitions(self, role_scope: str, **kwargs: "Any") -> "AsyncItemPaged[KeyVaultRoleDefinition]": + """List all role definitions applicable at and above a scope. + + :param str role_scope: scope of the role definitions + :rtype: ~azure.core.async_paging.AsyncItemPaged[KeyVaultRoleDefinition] + """ + return self._client.role_definitions.list( + self._vault_url, + role_scope, + cls=lambda result: [KeyVaultRoleDefinition._from_generated(d) for d in result], + **kwargs + ) diff --git a/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control.test_list_role_definitions.yaml b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control.test_list_role_definitions.yaml new file mode 100644 index 000000000000..619557270b11 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control.test_list_role_definitions.yaml @@ -0,0 +1,69 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: !!python/unicode OK + headers: + content-length: + - '2' + content-type: + - application/json + www-authenticate: + - Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"value":[{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","name":"a290e904-7015-4bba-90c8-60543313cdb4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/515eb02d-2335-4d2d-92f2-b1cbdf9c3778","name":"515eb02d-2335-4d2d-92f2-b1cbdf9c3778","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Officer","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b","name":"21dbd100-6940-42c2-9190-5d6cb909625b","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto User","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/4bd23610-cdcf-4971-bdee-bdc562cc28e4","name":"4bd23610-cdcf-4971-bdee-bdc562cc28e4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Policy Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","name":"2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Auditor","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/33413926-3206-4cdd-b39a-83574fe37a17","name":"33413926-3206-4cdd-b39a-83574fe37a17","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Service Encryption","type":""},"type":"Microsoft.Authorization/roleDefinitions"}]}' + headers: + content-length: + - '5517' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control.test_role_assignment.yaml b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control.test_role_assignment.yaml new file mode 100644 index 000000000000..595db694da16 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control.test_role_assignment.yaml @@ -0,0 +1,226 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: !!python/unicode OK + headers: + content-length: + - '2' + content-type: + - application/json + www-authenticate: + - Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"value":[{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","name":"a290e904-7015-4bba-90c8-60543313cdb4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/515eb02d-2335-4d2d-92f2-b1cbdf9c3778","name":"515eb02d-2335-4d2d-92f2-b1cbdf9c3778","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Officer","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b","name":"21dbd100-6940-42c2-9190-5d6cb909625b","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto User","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/4bd23610-cdcf-4971-bdee-bdc562cc28e4","name":"4bd23610-cdcf-4971-bdee-bdc562cc28e4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Policy Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","name":"2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Auditor","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/33413926-3206-4cdd-b39a-83574fe37a17","name":"33413926-3206-4cdd-b39a-83574fe37a17","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Service Encryption","type":""},"type":"Microsoft.Authorization/roleDefinitions"}]}' + headers: + content-length: + - '5517' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 200 + message: OK +- request: + body: !!python/unicode '{"properties": {"roleDefinitionId": "Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4", + "principalId": "service-principal-id"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '200' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: PUT + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}' + headers: + content-length: + - '398' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}' + headers: + content-length: + - '398' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"value":[{"id":"/providers/Microsoft.Authorization/roleAssignments/e1392147-41b5-498b-847d-ca061e8808a3","name":"e1392147-41b5-498b-847d-ca061e8808a3","properties":{"principalId":"67ca7f59-968b-4cde-8582-d6a5341fa721","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/f35aa2fd-545a-4f42-a44b-f862a530d4f1","name":"f35aa2fd-545a-4f42-a44b-f862a530d4f1","properties":{"principalId":"f84ae8f9-c979-4750-a2fe-b350a00bebff","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/457acfe4-7ff8-4608-b3ac-87139804539e","name":"457acfe4-7ff8-4608-b3ac-87139804539e","properties":{"principalId":"693a17da-7022-4cdd-9d4e-4e72e4ad449d","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/c6de6e40-d764-49e1-8e7c-be2f2a27de81","name":"c6de6e40-d764-49e1-8e7c-be2f2a27de81","properties":{"principalId":"3c1303ad-140b-493c-ab45-bed8ddbfa72c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/2f070682-b1a6-0ad3-acd3-7b891e5c79b0","name":"2f070682-b1a6-0ad3-acd3-7b891e5c79b0","properties":{"principalId":"bf0cee9f-b26b-4e25-b4ab-92ec7466cf33","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/0480f9fc-1294-4668-b31e-e5d8bae7d5b3","name":"0480f9fc-1294-4668-b31e-e5d8bae7d5b3","properties":{"principalId":"74677558-f369-4792-afe5-f99738b5fa7c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}]}' + headers: + content-length: + - '2804' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: DELETE + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}' + headers: + content-length: + - '398' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/2.7.15 (Windows-10-10.0.19041) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2-preview + response: + body: + string: !!python/unicode '{"value":[{"id":"/providers/Microsoft.Authorization/roleAssignments/e1392147-41b5-498b-847d-ca061e8808a3","name":"e1392147-41b5-498b-847d-ca061e8808a3","properties":{"principalId":"67ca7f59-968b-4cde-8582-d6a5341fa721","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/f35aa2fd-545a-4f42-a44b-f862a530d4f1","name":"f35aa2fd-545a-4f42-a44b-f862a530d4f1","properties":{"principalId":"f84ae8f9-c979-4750-a2fe-b350a00bebff","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/457acfe4-7ff8-4608-b3ac-87139804539e","name":"457acfe4-7ff8-4608-b3ac-87139804539e","properties":{"principalId":"693a17da-7022-4cdd-9d4e-4e72e4ad449d","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/c6de6e40-d764-49e1-8e7c-be2f2a27de81","name":"c6de6e40-d764-49e1-8e7c-be2f2a27de81","properties":{"principalId":"3c1303ad-140b-493c-ab45-bed8ddbfa72c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/2f070682-b1a6-0ad3-acd3-7b891e5c79b0","name":"2f070682-b1a6-0ad3-acd3-7b891e5c79b0","properties":{"principalId":"bf0cee9f-b26b-4e25-b4ab-92ec7466cf33","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/0480f9fc-1294-4668-b31e-e5d8bae7d5b3","name":"0480f9fc-1294-4668-b31e-e5d8bae7d5b3","properties":{"principalId":"74677558-f369-4792-afe5-f99738b5fa7c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}]}' + headers: + content-length: + - '2405' + content-type: + - application/json + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=24.17.201.78 + x-ms-keyvault-region: + - EASTUS + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control_async.test_list_role_definitions.yaml b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control_async.test_list_role_definitions.yaml new file mode 100644 index 000000000000..131a7d6c32bc --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control_async.test_list_role_definitions.yaml @@ -0,0 +1,54 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Content-Length: + - '0' + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: OK + headers: + content-length: '2' + content-type: application/json + www-authenticate: Bearer authorization="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://managedhsm.azure.net" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: '{"value":[{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","name":"a290e904-7015-4bba-90c8-60543313cdb4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/515eb02d-2335-4d2d-92f2-b1cbdf9c3778","name":"515eb02d-2335-4d2d-92f2-b1cbdf9c3778","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Officer","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b","name":"21dbd100-6940-42c2-9190-5d6cb909625b","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto User","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/4bd23610-cdcf-4971-bdee-bdc562cc28e4","name":"4bd23610-cdcf-4971-bdee-bdc562cc28e4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Policy Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","name":"2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Auditor","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/33413926-3206-4cdd-b39a-83574fe37a17","name":"33413926-3206-4cdd-b39a-83574fe37a17","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Service Encryption","type":""},"type":"Microsoft.Authorization/roleDefinitions"}]}' + headers: + content-length: '5517' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 200 + message: OK + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview +version: 1 diff --git a/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control_async.test_role_assignment.yaml b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control_async.test_role_assignment.yaml new file mode 100644 index 000000000000..a884c896a2ea --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/tests/recordings/test_access_control_async.test_role_assignment.yaml @@ -0,0 +1,145 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview + response: + body: + string: '{"value":[{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","name":"a290e904-7015-4bba-90c8-60543313cdb4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/515eb02d-2335-4d2d-92f2-b1cbdf9c3778","name":"515eb02d-2335-4d2d-92f2-b1cbdf9c3778","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/restore/action","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/delete","Microsoft.KeyVault/managedHsm/keys/export/action","Microsoft.KeyVault/managedHsm/keys/import/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Officer","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b","name":"21dbd100-6940-42c2-9190-5d6cb909625b","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/write/action","Microsoft.KeyVault/managedHsm/keys/backup/action","Microsoft.KeyVault/managedHsm/keys/create","Microsoft.KeyVault/managedHsm/keys/encrypt/action","Microsoft.KeyVault/managedHsm/keys/decrypt/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action","Microsoft.KeyVault/managedHsm/keys/sign/action","Microsoft.KeyVault/managedHsm/keys/verify/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto User","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/4bd23610-cdcf-4971-bdee-bdc562cc28e4","name":"4bd23610-cdcf-4971-bdee-bdc562cc28e4","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/roleDefinitions/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/read/action","Microsoft.KeyVault/managedHsm/roleAssignments/write/action","Microsoft.KeyVault/managedHsm/roleAssignments/delete/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Policy Administrator","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","name":"2c18b078-7c48-4d3a-af88-5a3a1b3f82b3","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Auditor","type":""},"type":"Microsoft.Authorization/roleDefinitions"},{"id":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/33413926-3206-4cdd-b39a-83574fe37a17","name":"33413926-3206-4cdd-b39a-83574fe37a17","properties":{"assignableScopes":["/"],"description":"","permissions":[{"actions":[],"dataActions":["Microsoft.KeyVault/managedHsm/keys/read/action","Microsoft.KeyVault/managedHsm/keys/wrap/action","Microsoft.KeyVault/managedHsm/keys/unwrap/action"],"notActions":[],"notDataActions":[]}],"roleName":"Azure + Key Vault Managed HSM Crypto Service Encryption","type":""},"type":"Microsoft.Authorization/roleDefinitions"}]}' + headers: + content-length: '5517' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 200 + message: OK + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2-preview +- request: + body: '{"properties": {"roleDefinitionId": "Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4", + "principalId": "service-principal-id"}}' + headers: + Accept: + - application/json + Content-Length: + - '200' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: PUT + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2-preview + response: + body: + string: '{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}' + headers: + content-length: '398' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 201 + message: Created + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleAssignments/4af0820d-e870-4795-878e-1869f6f0888e?api-version=7.2-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2-preview + response: + body: + string: '{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}' + headers: + content-length: '398' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 200 + message: OK + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleAssignments/4af0820d-e870-4795-878e-1869f6f0888e?api-version=7.2-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2-preview + response: + body: + string: '{"value":[{"id":"/providers/Microsoft.Authorization/roleAssignments/e1392147-41b5-498b-847d-ca061e8808a3","name":"e1392147-41b5-498b-847d-ca061e8808a3","properties":{"principalId":"67ca7f59-968b-4cde-8582-d6a5341fa721","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/f35aa2fd-545a-4f42-a44b-f862a530d4f1","name":"f35aa2fd-545a-4f42-a44b-f862a530d4f1","properties":{"principalId":"f84ae8f9-c979-4750-a2fe-b350a00bebff","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/457acfe4-7ff8-4608-b3ac-87139804539e","name":"457acfe4-7ff8-4608-b3ac-87139804539e","properties":{"principalId":"693a17da-7022-4cdd-9d4e-4e72e4ad449d","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/c6de6e40-d764-49e1-8e7c-be2f2a27de81","name":"c6de6e40-d764-49e1-8e7c-be2f2a27de81","properties":{"principalId":"3c1303ad-140b-493c-ab45-bed8ddbfa72c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/2f070682-b1a6-0ad3-acd3-7b891e5c79b0","name":"2f070682-b1a6-0ad3-acd3-7b891e5c79b0","properties":{"principalId":"bf0cee9f-b26b-4e25-b4ab-92ec7466cf33","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/0480f9fc-1294-4668-b31e-e5d8bae7d5b3","name":"0480f9fc-1294-4668-b31e-e5d8bae7d5b3","properties":{"principalId":"74677558-f369-4792-afe5-f99738b5fa7c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}]}' + headers: + content-length: '2804' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 200 + message: OK + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: DELETE + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2-preview + response: + body: + string: '{"id":"/providers/Microsoft.Authorization/roleAssignments/some-uuid","name":"some-uuid","properties":{"principalId":"service-principal-id","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}' + headers: + content-length: '398' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 200 + message: OK + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleAssignments/4af0820d-e870-4795-878e-1869f6f0888e?api-version=7.2-preview +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-keyvault-administration/1.0.0b1 Python/3.5.4 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2-preview + response: + body: + string: '{"value":[{"id":"/providers/Microsoft.Authorization/roleAssignments/e1392147-41b5-498b-847d-ca061e8808a3","name":"e1392147-41b5-498b-847d-ca061e8808a3","properties":{"principalId":"67ca7f59-968b-4cde-8582-d6a5341fa721","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/f35aa2fd-545a-4f42-a44b-f862a530d4f1","name":"f35aa2fd-545a-4f42-a44b-f862a530d4f1","properties":{"principalId":"f84ae8f9-c979-4750-a2fe-b350a00bebff","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/457acfe4-7ff8-4608-b3ac-87139804539e","name":"457acfe4-7ff8-4608-b3ac-87139804539e","properties":{"principalId":"693a17da-7022-4cdd-9d4e-4e72e4ad449d","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/c6de6e40-d764-49e1-8e7c-be2f2a27de81","name":"c6de6e40-d764-49e1-8e7c-be2f2a27de81","properties":{"principalId":"3c1303ad-140b-493c-ab45-bed8ddbfa72c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/2f070682-b1a6-0ad3-acd3-7b891e5c79b0","name":"2f070682-b1a6-0ad3-acd3-7b891e5c79b0","properties":{"principalId":"bf0cee9f-b26b-4e25-b4ab-92ec7466cf33","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"},{"id":"/providers/Microsoft.Authorization/roleAssignments/0480f9fc-1294-4668-b31e-e5d8bae7d5b3","name":"0480f9fc-1294-4668-b31e-e5d8bae7d5b3","properties":{"principalId":"74677558-f369-4792-afe5-f99738b5fa7c","roleDefinitionId":"Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/a290e904-7015-4bba-90c8-60543313cdb4","scope":"/"},"type":"Microsoft.Authorization/roleAssignments"}]}' + headers: + content-length: '2405' + content-type: application/json + x-content-type-options: nosniff + x-ms-keyvault-network-info: addr=24.17.201.78 + x-ms-keyvault-region: EASTUS + status: + code: 200 + message: OK + url: https://eastus.clitest.managedhsm-preview.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2-preview +version: 1 diff --git a/sdk/keyvault/azure-keyvault-administration/tests/test_access_control.py b/sdk/keyvault/azure-keyvault-administration/tests/test_access_control.py new file mode 100644 index 000000000000..1bdbf40fb365 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/tests/test_access_control.py @@ -0,0 +1,95 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import functools +import os +import uuid + +from azure.keyvault.administration import KeyVaultAccessControlClient +from devtools_testutils import KeyVaultPreparer, ResourceGroupPreparer +import pytest + +from _shared.test_case import KeyVaultTestCase +from _shared.preparer import KeyVaultClientPreparer as _KeyVaultClientPreparer + +AccessControlClientPreparer = functools.partial(_KeyVaultClientPreparer, KeyVaultAccessControlClient) + + +class AccessControlTests(KeyVaultTestCase): + def __init__(self, *args, **kwargs): + super(AccessControlTests, self).__init__(*args, **kwargs) + if self.is_live: + pytest.skip("test infrastructure can't yet create a Key Vault supporting the RBAC API") + + def get_replayable_uuid(self, replay_value): + if self.is_live: + value = str(uuid.uuid4()) + self.scrubber.register_name_pair(value, replay_value) + return value + return replay_value + + def get_service_principal_id(self): + replay_value = "service-principal-id" + if self.is_live: + value = os.environ["AZURE_CLIENT_ID"] + self.scrubber.register_name_pair(value, replay_value) + return value + return replay_value + + @ResourceGroupPreparer(random_name_enabled=True) + @KeyVaultPreparer() + @AccessControlClientPreparer() + def test_list_role_definitions(self, client): + definitions = [d for d in client.list_role_definitions("/")] + assert len(definitions) + + for definition in definitions: + assert "/" in definition.assignable_scopes + assert definition.description is not None + assert definition.id is not None + assert definition.name is not None + assert len(definition.permissions) + assert definition.role_name is not None + assert definition.role_type is not None + assert definition.type is not None + + @ResourceGroupPreparer(random_name_enabled=True) + @KeyVaultPreparer() + @AccessControlClientPreparer() + def test_role_assignment(self, client): + scope = "/" + definitions = [d for d in client.list_role_definitions(scope)] + + # assign an arbitrary role to the service principal authenticating these requests + definition = definitions[0] + principal_id = self.get_service_principal_id() + name = self.get_replayable_uuid("some-uuid") + + created = client.create_role_assignment(scope, name, definition.id, principal_id) + assert created.name == name + assert created.principal_id == principal_id + assert created.role_definition_id == definition.id + assert created.scope == scope + + # should be able to get the new assignment + got = client.get_role_assignment(scope, name) + assert got.name == name + assert got.principal_id == principal_id + assert got.role_definition_id == definition.id + assert got.scope == scope + + # new assignment should be in the list of all assignments + matching_assignments = [ + a for a in client.list_role_assignments(scope) if a.assignment_id == created.assignment_id + ] + assert len(matching_assignments) == 1 + + # delete the assignment + deleted = client.delete_role_assignment(scope, created.name) + assert deleted.name == created.name + assert deleted.assignment_id == created.assignment_id + assert deleted.scope == scope + assert deleted.role_definition_id == created.role_definition_id + + assert not any(a for a in client.list_role_assignments(scope) if a.assignment_id == created.assignment_id) diff --git a/sdk/keyvault/azure-keyvault-administration/tests/test_access_control_async.py b/sdk/keyvault/azure-keyvault-administration/tests/test_access_control_async.py new file mode 100644 index 000000000000..feb85c5a1e98 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-administration/tests/test_access_control_async.py @@ -0,0 +1,101 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import functools +import os +import uuid + +from azure.keyvault.administration.aio import KeyVaultAccessControlClient +from devtools_testutils import KeyVaultPreparer, ResourceGroupPreparer +import pytest + +from _shared.test_case_async import KeyVaultTestCase +from _shared.preparer_async import KeyVaultClientPreparer as _KeyVaultClientPreparer + +AccessControlClientPreparer = functools.partial(_KeyVaultClientPreparer, KeyVaultAccessControlClient) + + +class AccessControlTests(KeyVaultTestCase): + def __init__(self, *args, **kwargs): + super(AccessControlTests, self).__init__(*args, **kwargs) + if self.is_live: + pytest.skip("test infrastructure can't yet create a Key Vault supporting the RBAC API") + + def get_replayable_uuid(self, replay_value): + if self.is_live: + value = str(uuid.uuid4()) + self.scrubber.register_name_pair(value, replay_value) + return value + return replay_value + + def get_service_principal_id(self): + replay_value = "service-principal-id" + if self.is_live: + value = os.environ["AZURE_CLIENT_ID"] + self.scrubber.register_name_pair(value, replay_value) + return value + return replay_value + + @ResourceGroupPreparer(random_name_enabled=True) + @KeyVaultPreparer() + @AccessControlClientPreparer() + async def test_list_role_definitions(self, client): + definitions = [] + async for definition in client.list_role_definitions("/"): + definitions.append(definition) + assert len(definitions) + + for definition in definitions: + assert "/" in definition.assignable_scopes + assert definition.description is not None + assert definition.id is not None + assert definition.name is not None + assert len(definition.permissions) + assert definition.role_name is not None + assert definition.role_type is not None + assert definition.type is not None + + @ResourceGroupPreparer(random_name_enabled=True) + @KeyVaultPreparer() + @AccessControlClientPreparer() + async def test_role_assignment(self, client): + scope = "/" + definitions = [] + async for definition in client.list_role_definitions("/"): + definitions.append(definition) + + # assign an arbitrary role to the service principal authenticating these requests + definition = definitions[0] + principal_id = self.get_service_principal_id() + name = self.get_replayable_uuid("some-uuid") + + created = await client.create_role_assignment(scope, name, definition.id, principal_id) + assert created.name == name + assert created.principal_id == principal_id + assert created.role_definition_id == definition.id + assert created.scope == scope + + # should be able to get the new assignment + got = await client.get_role_assignment(scope, name) + assert got.name == name + assert got.principal_id == principal_id + assert got.role_definition_id == definition.id + assert got.scope == scope + + # new assignment should be in the list of all assignments + matching_assignments = [] + async for assignment in client.list_role_assignments(scope): + if assignment.assignment_id == created.assignment_id: + matching_assignments.append(assignment) + assert len(matching_assignments) == 1 + + # delete the assignment + deleted = await client.delete_role_assignment(scope, created.name) + assert deleted.name == created.name + assert deleted.assignment_id == created.assignment_id + assert deleted.scope == scope + assert deleted.role_definition_id == created.role_definition_id + + async for assignment in client.list_role_assignments(scope): + assert assignment.assignment_id != created.assignment_id, "the role assignment should have been deleted" diff --git a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md index aa8bab5357fc..e19c22c00e7b 100644 --- a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md @@ -1,6 +1,8 @@ # Release History ## 4.2.1 (Unreleased) +### Fixed +- Correct typing for paging methods ## 4.2.0 (2020-08-11) diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_client.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_client.py index 630c4e8d2920..c515772c1b4d 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_client.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_client.py @@ -31,7 +31,8 @@ if TYPE_CHECKING: # pylint:disable=unused-import - from typing import Any, Dict, List, Optional, Iterable + from typing import Any, Dict, Iterable, List, Optional + from azure.core.paging import ItemPaged class CertificateClient(KeyVaultClientBase): @@ -530,7 +531,7 @@ def restore_certificate_backup(self, backup, **kwargs): @distributed_trace def list_deleted_certificates(self, **kwargs): - # type: (**Any) -> Iterable[DeletedCertificate] + # type: (**Any) -> ItemPaged[DeletedCertificate] """Lists the currently-recoverable deleted certificates. Possible only if vault is soft-delete enabled. Requires certificates/get/list permission. Retrieves the certificates in the current vault which @@ -566,7 +567,7 @@ def list_deleted_certificates(self, **kwargs): @distributed_trace def list_properties_of_certificates(self, **kwargs): - # type: (**Any) -> Iterable[CertificateProperties] + # type: (**Any) -> ItemPaged[CertificateProperties] """List identifiers and properties of all certificates in the vault. Requires certificates/list permission. @@ -598,7 +599,7 @@ def list_properties_of_certificates(self, **kwargs): @distributed_trace def list_properties_of_certificate_versions(self, certificate_name, **kwargs): - # type: (str, **Any) -> Iterable[CertificateProperties] + # type: (str, **Any) -> ItemPaged[CertificateProperties] """List the identifiers and properties of a certificate's versions. Requires certificates/list permission. @@ -989,7 +990,7 @@ def delete_issuer(self, issuer_name, **kwargs): @distributed_trace def list_properties_of_issuers(self, **kwargs): - # type: (**Any) -> Iterable[IssuerProperties] + # type: (**Any) -> ItemPaged[IssuerProperties] """Lists properties of the certificate issuers for the key vault. Requires the certificates/manageissuers/getissuers permission. diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/aio/_client.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/aio/_client.py index 62c1534810ed..033c4aebb1c2 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/aio/_client.py @@ -4,12 +4,13 @@ # ------------------------------------ # pylint:disable=too-many-lines,too-many-public-methods import base64 -from typing import Any, AsyncIterable, Optional, Iterable, List, Dict, Union +from typing import Any, Optional, Iterable, List, Dict, Union from functools import partial from azure.core.polling import async_poller from azure.core.tracing.decorator import distributed_trace from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.async_paging import AsyncItemPaged from .. import ( KeyVaultCertificate, @@ -504,7 +505,7 @@ async def restore_certificate_backup(self, backup: bytes, **kwargs: "Any") -> Ke return KeyVaultCertificate._from_certificate_bundle(certificate_bundle=bundle) @distributed_trace - def list_deleted_certificates(self, **kwargs: "Any") -> AsyncIterable[DeletedCertificate]: + def list_deleted_certificates(self, **kwargs: "Any") -> AsyncItemPaged[DeletedCertificate]: """Lists the currently-recoverable deleted certificates. Possible only if vault is soft-delete enabled. Requires certificates/get/list permission. Retrieves the certificates in the current vault which @@ -536,7 +537,7 @@ def list_deleted_certificates(self, **kwargs: "Any") -> AsyncIterable[DeletedCer ) @distributed_trace - def list_properties_of_certificates(self, **kwargs: "Any") -> AsyncIterable[CertificateProperties]: + def list_properties_of_certificates(self, **kwargs: "Any") -> AsyncItemPaged[CertificateProperties]: """List identifiers and properties of all certificates in the vault. Requires certificates/list permission. @@ -568,7 +569,7 @@ def list_properties_of_certificates(self, **kwargs: "Any") -> AsyncIterable[Cert @distributed_trace def list_properties_of_certificate_versions( self, certificate_name: str, **kwargs: "Any" - ) -> AsyncIterable[CertificateProperties]: + ) -> AsyncItemPaged[CertificateProperties]: """List the identifiers and properties of a certificate's versions. Requires certificates/list permission. @@ -965,7 +966,7 @@ async def delete_issuer(self, issuer_name: str, **kwargs: "Any") -> CertificateI return CertificateIssuer._from_issuer_bundle(issuer_bundle=issuer_bundle) @distributed_trace - def list_properties_of_issuers(self, **kwargs: "Any") -> AsyncIterable[IssuerProperties]: + def list_properties_of_issuers(self, **kwargs: "Any") -> AsyncItemPaged[IssuerProperties]: """Lists properties of the certificate issuers for the key vault. Requires the certificates/manageissuers/getissuers permission. diff --git a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md index 7f2ecd50a554..64279ca98878 100644 --- a/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-keys/CHANGELOG.md @@ -1,6 +1,8 @@ # Release History ## 4.2.1 (Unreleased) +### Fixed +- Correct typing for async paging methods ## 4.2.0 (2020-08-11) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py index f939e7afe87c..3b7b1cf070f6 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py @@ -16,7 +16,8 @@ if TYPE_CHECKING: # pylint:disable=ungrouped-imports from datetime import datetime - from typing import Any, AsyncIterable, Optional, List, Union + from azure.core.async_paging import AsyncItemPaged + from typing import Any, Optional, List, Union from .. import KeyType @@ -256,7 +257,7 @@ async def get_deleted_key(self, name: str, **kwargs: "Any") -> DeletedKey: return DeletedKey._from_deleted_key_bundle(bundle) @distributed_trace - def list_deleted_keys(self, **kwargs: "Any") -> "AsyncIterable[DeletedKey]": + def list_deleted_keys(self, **kwargs: "Any") -> "AsyncItemPaged[DeletedKey]": """List all deleted keys, including the public part of each. Possible only in a vault with soft-delete enabled. Requires keys/list permission. @@ -281,7 +282,7 @@ def list_deleted_keys(self, **kwargs: "Any") -> "AsyncIterable[DeletedKey]": ) @distributed_trace - def list_properties_of_keys(self, **kwargs: "Any") -> "AsyncIterable[KeyProperties]": + def list_properties_of_keys(self, **kwargs: "Any") -> "AsyncItemPaged[KeyProperties]": """List identifiers and properties of all keys in the vault. Requires keys/list permission. :returns: An iterator of keys without their cryptographic material or version information @@ -304,7 +305,7 @@ def list_properties_of_keys(self, **kwargs: "Any") -> "AsyncIterable[KeyProperti ) @distributed_trace - def list_properties_of_key_versions(self, name: str, **kwargs: "Any") -> "AsyncIterable[KeyProperties]": + def list_properties_of_key_versions(self, name: str, **kwargs: "Any") -> "AsyncItemPaged[KeyProperties]": """List the identifiers and properties of a key's versions. Requires keys/list permission. :param str name: The name of the key diff --git a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md index 3498b39e68cb..7d60250d6f86 100644 --- a/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md @@ -1,7 +1,8 @@ # Release History ## 4.2.1 (Unreleased) - +### Fixed +- Correct typing for async paging methods ## 4.2.0 (2020-08-11) ### Fixed diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py index 0ae4b19f52ea..7f71232c2f98 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py @@ -2,11 +2,12 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from typing import Any, AsyncIterable, Optional, Dict +from typing import Any, Optional, Dict from functools import partial from azure.core.tracing.decorator import distributed_trace from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.async_paging import AsyncItemPaged from .._models import KeyVaultSecret, DeletedSecret, SecretProperties from .._shared import AsyncKeyVaultClientBase @@ -165,7 +166,7 @@ async def update_secret_properties( return SecretProperties._from_secret_bundle(bundle) # pylint: disable=protected-access @distributed_trace - def list_properties_of_secrets(self, **kwargs: "Any") -> AsyncIterable[SecretProperties]: + def list_properties_of_secrets(self, **kwargs: "Any") -> AsyncItemPaged[SecretProperties]: """List identifiers and attributes of all secrets in the vault. Requires secrets/list permission. List items don't include secret values. Use :func:`get_secret` to get a secret's value. @@ -189,7 +190,7 @@ def list_properties_of_secrets(self, **kwargs: "Any") -> AsyncIterable[SecretPro ) @distributed_trace - def list_properties_of_secret_versions(self, name: str, **kwargs: "Any") -> AsyncIterable[SecretProperties]: + def list_properties_of_secret_versions(self, name: str, **kwargs: "Any") -> AsyncItemPaged[SecretProperties]: """List properties of all versions of a secret, excluding their values. Requires secrets/list permission. List items don't include secret values. Use :func:`get_secret` to get a secret's value. @@ -321,7 +322,7 @@ async def get_deleted_secret(self, name: str, **kwargs: "Any") -> DeletedSecret: return DeletedSecret._from_deleted_secret_bundle(bundle) @distributed_trace - def list_deleted_secrets(self, **kwargs: "Any") -> AsyncIterable[DeletedSecret]: + def list_deleted_secrets(self, **kwargs: "Any") -> AsyncItemPaged[DeletedSecret]: """Lists all deleted secrets. Possible only in vaults with soft-delete enabled. Requires secrets/list permission. diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index 3556002dbdab..a8bf7de0cf3d 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -2,6 +2,10 @@ ## 7.0.0b6 (Unreleased) +**Breaking Changes** + +* `ServiceBusClient.close()` now closes spawned senders and receivers. +* Attempting to initialize a sender or receiver with a different connection string entity and specified entity (e.g. `queue_name`) will result in an AuthenticationError ## 7.0.0b5 (2020-08-10) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index 223180d1bf11..80b181d116a9 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -21,7 +21,7 @@ from ._common._configuration import Configuration from .exceptions import ( ServiceBusError, - ServiceBusAuthorizationError, + ServiceBusAuthenticationError, _create_servicebus_exception ) from ._common.utils import create_properties @@ -104,7 +104,7 @@ def _convert_connection_string_to_kwargs(conn_str, shared_key_credential_type, * entity_in_kwargs = queue_name or topic_name if entity_in_conn_str and entity_in_kwargs and (entity_in_conn_str != entity_in_kwargs): - raise ServiceBusAuthorizationError( + raise ServiceBusAuthenticationError( "Entity names do not match, the entity name in connection string is {};" " the entity name in parameter is {}.".format(entity_in_conn_str, entity_in_kwargs) ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py index 28f253bdfdf2..be27075ea2de 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py @@ -2,11 +2,12 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from typing import Any, TYPE_CHECKING +from typing import Any, List, TYPE_CHECKING +import logging import uamqp -from ._base_handler import _parse_conn_str, ServiceBusSharedKeyCredential +from ._base_handler import _parse_conn_str, ServiceBusSharedKeyCredential, BaseHandler from ._servicebus_sender import ServiceBusSender from ._servicebus_receiver import ServiceBusReceiver from ._servicebus_session_receiver import ServiceBusSessionReceiver @@ -16,6 +17,8 @@ if TYPE_CHECKING: from azure.core.credentials import TokenCredential +_LOGGER = logging.getLogger(__name__) + class ServiceBusClient(object): """The ServiceBusClient class defines a high level interface for @@ -69,6 +72,7 @@ def __init__( self._auth_uri = "{}/{}".format(self._auth_uri, self._entity_name) # Internal flag for switching whether to apply connection sharing, pending fix in uamqp library self._connection_sharing = False + self._handlers = [] # type: List[BaseHandler] def __enter__(self): if self._connection_sharing: @@ -89,10 +93,22 @@ def _create_uamqp_connection(self): def close(self): # type: () -> None """ - Close down the ServiceBus client and the underlying connection. + Close down the ServiceBus client. + All spawned senders, receivers and underlying connection will be shutdown. :return: None """ + for handler in self._handlers: + try: + handler.close() + except Exception as exception: # pylint: disable=broad-except + _LOGGER.error( + "Client has met an exception when closing the handler: %r. Exception: %r.", + handler._container_id, # pylint: disable=protected-access + exception, + ) + del self._handlers[:] + if self._connection_sharing and self._connection: self._connection.destroy() @@ -157,7 +173,7 @@ def get_queue_sender(self, queue_name, **kwargs): """ # pylint: disable=protected-access - return ServiceBusSender( + handler = ServiceBusSender( fully_qualified_namespace=self.fully_qualified_namespace, queue_name=queue_name, credential=self._credential, @@ -168,6 +184,8 @@ def get_queue_sender(self, queue_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_queue_receiver(self, queue_name, **kwargs): # type: (str, Any) -> ServiceBusReceiver @@ -205,7 +223,7 @@ def get_queue_receiver(self, queue_name, **kwargs): """ # pylint: disable=protected-access - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, queue_name=queue_name, credential=self._credential, @@ -216,6 +234,8 @@ def get_queue_receiver(self, queue_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_queue_deadletter_receiver(self, queue_name, **kwargs): # type: (str, Any) -> ServiceBusReceiver @@ -265,7 +285,7 @@ def get_queue_deadletter_receiver(self, queue_name, **kwargs): queue_name=queue_name, transfer_deadletter=kwargs.get('transfer_deadletter', False) ) - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, entity_name=entity_name, credential=self._credential, @@ -277,6 +297,8 @@ def get_queue_deadletter_receiver(self, queue_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_topic_sender(self, topic_name, **kwargs): # type: (str, Any) -> ServiceBusSender @@ -300,7 +322,7 @@ def get_topic_sender(self, topic_name, **kwargs): :caption: Create a new instance of the ServiceBusSender from ServiceBusClient. """ - return ServiceBusSender( + handler = ServiceBusSender( fully_qualified_namespace=self.fully_qualified_namespace, topic_name=topic_name, credential=self._credential, @@ -311,6 +333,8 @@ def get_topic_sender(self, topic_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): # type: (str, str, Any) -> ServiceBusReceiver @@ -353,7 +377,7 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): """ # pylint: disable=protected-access - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, topic_name=topic_name, subscription_name=subscription_name, @@ -365,6 +389,8 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_subscription_deadletter_receiver(self, topic_name, subscription_name, **kwargs): # type: (str, str, Any) -> ServiceBusReceiver @@ -416,7 +442,7 @@ def get_subscription_deadletter_receiver(self, topic_name, subscription_name, ** subscription_name=subscription_name, transfer_deadletter=kwargs.get('transfer_deadletter', False) ) - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, entity_name=entity_name, credential=self._credential, @@ -428,6 +454,8 @@ def get_subscription_deadletter_receiver(self, topic_name, subscription_name, ** user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_subscription_session_receiver(self, topic_name, subscription_name, session_id=None, **kwargs): # type: (str, str, str, Any) -> ServiceBusSessionReceiver @@ -473,7 +501,7 @@ def get_subscription_session_receiver(self, topic_name, subscription_name, sessi """ # pylint: disable=protected-access - return ServiceBusSessionReceiver( + handler = ServiceBusSessionReceiver( fully_qualified_namespace=self.fully_qualified_namespace, topic_name=topic_name, subscription_name=subscription_name, @@ -486,6 +514,8 @@ def get_subscription_session_receiver(self, topic_name, subscription_name, sessi user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): # type: (str, str, Any) -> ServiceBusSessionReceiver @@ -526,7 +556,7 @@ def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): """ # pylint: disable=protected-access - return ServiceBusSessionReceiver( + handler = ServiceBusSessionReceiver( fully_qualified_namespace=self.fully_qualified_namespace, queue_name=queue_name, credential=self._credential, @@ -538,3 +568,5 @@ def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py index 130ff7a77f7e..883a291581c3 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -352,6 +352,9 @@ def from_connection_string( within its request to the service. :rtype: ~azure.servicebus.ServiceBusReceiver + :raises ~azure.servicebus.ServiceBusAuthenticationError: Indicates an issue in token/identity validity. + :raises ~azure.servicebus.ServiceBusAuthorizationError: Indicates an access/rights related failure. + .. admonition:: Example: .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index a5a5131cac22..2ba095b5f2f6 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -284,6 +284,9 @@ def from_connection_string( :rtype: ~azure.servicebus.ServiceBusSender + :raises ~azure.servicebus.ServiceBusAuthenticationError: Indicates an issue in token/identity validity. + :raises ~azure.servicebus.ServiceBusAuthorizationError: Indicates an access/rights related failure. + .. admonition:: Example: .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py index e69d30d2f847..c6a6bcc33d35 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py @@ -154,6 +154,9 @@ def from_connection_string( within its request to the service. :rtype: ~azure.servicebus.ServiceBusSessionReceiver + :raises ~azure.servicebus.ServiceBusAuthenticationError: Indicates an issue in token/identity validity. + :raises ~azure.servicebus.ServiceBusAuthorizationError: Indicates an access/rights related failure. + .. admonition:: Example: .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py index 8db9f20d7b1c..4e13bf070d49 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py @@ -18,7 +18,7 @@ 'ServiceBusSender', 'ServiceBusReceiver', 'ServiceBusSessionReceiver', + 'ServiceBusSession', 'ServiceBusSharedKeyCredential', - 'AutoLockRenew', - 'ServiceBusSession' + 'AutoLockRenew' ] diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py index 67cd7bff710c..a6827a8ae91a 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py @@ -2,12 +2,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from typing import Any, TYPE_CHECKING, Union +from typing import Any, List, TYPE_CHECKING +import logging import uamqp from .._base_handler import _parse_conn_str -from ._base_handler_async import ServiceBusSharedKeyCredential +from ._base_handler_async import ServiceBusSharedKeyCredential, BaseHandler from ._servicebus_sender_async import ServiceBusSender from ._servicebus_receiver_async import ServiceBusReceiver from ._servicebus_session_receiver_async import ServiceBusSessionReceiver @@ -18,6 +19,8 @@ if TYPE_CHECKING: from azure.core.credentials import TokenCredential +_LOGGER = logging.getLogger(__name__) + class ServiceBusClient(object): """The ServiceBusClient class defines a high level interface for @@ -71,6 +74,7 @@ def __init__( self._auth_uri = "{}/{}".format(self._auth_uri, self._entity_name) # Internal flag for switching whether to apply connection sharing, pending fix in uamqp library self._connection_sharing = False + self._handlers = [] # type: List[BaseHandler] async def __aenter__(self): if self._connection_sharing: @@ -133,9 +137,21 @@ async def close(self): # type: () -> None """ Close down the ServiceBus client. + All spawned senders, receivers and underlying connection will be shutdown. :return: None """ + for handler in self._handlers: + try: + await handler.close() + except Exception as exception: # pylint: disable=broad-except + _LOGGER.error( + "Client has met an exception when closing the handler: %r. Exception: %r.", + handler._container_id, # pylint: disable=protected-access + exception, + ) + del self._handlers[:] + if self._connection_sharing and self._connection: await self._connection.destroy_async() @@ -159,7 +175,7 @@ def get_queue_sender(self, queue_name, **kwargs): """ # pylint: disable=protected-access - return ServiceBusSender( + handler = ServiceBusSender( fully_qualified_namespace=self.fully_qualified_namespace, queue_name=queue_name, credential=self._credential, @@ -170,6 +186,8 @@ def get_queue_sender(self, queue_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_queue_receiver(self, queue_name, **kwargs): # type: (str, Any) -> ServiceBusReceiver @@ -206,7 +224,7 @@ def get_queue_receiver(self, queue_name, **kwargs): """ # pylint: disable=protected-access - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, queue_name=queue_name, credential=self._credential, @@ -217,6 +235,8 @@ def get_queue_receiver(self, queue_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_queue_deadletter_receiver(self, queue_name, **kwargs): # type: (str, Any) -> ServiceBusReceiver @@ -266,7 +286,7 @@ def get_queue_deadletter_receiver(self, queue_name, **kwargs): queue_name=queue_name, transfer_deadletter=kwargs.get('transfer_deadletter', False) ) - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, entity_name=entity_name, credential=self._credential, @@ -278,6 +298,8 @@ def get_queue_deadletter_receiver(self, queue_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_topic_sender(self, topic_name, **kwargs): # type: (str, Any) -> ServiceBusSender @@ -301,7 +323,7 @@ def get_topic_sender(self, topic_name, **kwargs): :caption: Create a new instance of the ServiceBusSender from ServiceBusClient. """ - return ServiceBusSender( + handler = ServiceBusSender( fully_qualified_namespace=self.fully_qualified_namespace, topic_name=topic_name, credential=self._credential, @@ -312,6 +334,8 @@ def get_topic_sender(self, topic_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): # type: (str, str, Any) -> ServiceBusReceiver @@ -354,7 +378,7 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): """ # pylint: disable=protected-access - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, topic_name=topic_name, subscription_name=subscription_name, @@ -366,6 +390,8 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_subscription_deadletter_receiver(self, topic_name, subscription_name, **kwargs): # type: (str, str, Any) -> ServiceBusReceiver @@ -417,7 +443,7 @@ def get_subscription_deadletter_receiver(self, topic_name, subscription_name, ** subscription_name=subscription_name, transfer_deadletter=kwargs.get('transfer_deadletter', False) ) - return ServiceBusReceiver( + handler = ServiceBusReceiver( fully_qualified_namespace=self.fully_qualified_namespace, entity_name=entity_name, credential=self._credential, @@ -429,6 +455,8 @@ def get_subscription_deadletter_receiver(self, topic_name, subscription_name, ** user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_subscription_session_receiver(self, topic_name, subscription_name, session_id=None, **kwargs): # type: (str, str, str, Any) -> ServiceBusSessionReceiver @@ -474,7 +502,7 @@ def get_subscription_session_receiver(self, topic_name, subscription_name, sessi """ # pylint: disable=protected-access - return ServiceBusSessionReceiver( + handler = ServiceBusSessionReceiver( fully_qualified_namespace=self.fully_qualified_namespace, topic_name=topic_name, subscription_name=subscription_name, @@ -487,6 +515,8 @@ def get_subscription_session_receiver(self, topic_name, subscription_name, sessi user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): # type: (str, str, Any) -> ServiceBusSessionReceiver @@ -526,7 +556,7 @@ def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): """ # pylint: disable=protected-access - return ServiceBusSessionReceiver( + handler = ServiceBusSessionReceiver( fully_qualified_namespace=self.fully_qualified_namespace, queue_name=queue_name, credential=self._credential, @@ -538,3 +568,5 @@ def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): user_agent=self._config.user_agent, **kwargs ) + self._handlers.append(handler) + return handler diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py index ac3a7a672f9d..da6c4b6e72e5 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py @@ -347,6 +347,9 @@ def from_connection_string( within its request to the service. :rtype: ~azure.servicebus.aio.ServiceBusReceiver + :raises ~azure.servicebus.ServiceBusAuthenticationError: Indicates an issue in token/identity validity. + :raises ~azure.servicebus.ServiceBusAuthorizationError: Indicates an access/rights related failure. + .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py index 4171981a4599..d427159074e7 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py @@ -223,6 +223,9 @@ def from_connection_string( :keyword str user_agent: If specified, this will be added in front of the built-in user agent string. :rtype: ~azure.servicebus.aio.ServiceBusSender + :raises ~azure.servicebus.ServiceBusAuthenticationError: Indicates an issue in token/identity validity. + :raises ~azure.servicebus.ServiceBusAuthorizationError: Indicates an access/rights related failure. + .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_receiver_async.py index ffc808f7b819..10abb88b5bf0 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_receiver_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_receiver_async.py @@ -137,6 +137,9 @@ def from_connection_string( within its request to the service. :rtype: ~azure.servicebus.aio.ServiceBusSessionReceiver + :raises ~azure.servicebus.ServiceBusAuthenticationError: Indicates an issue in token/identity validity. + :raises ~azure.servicebus.ServiceBusAuthorizationError: Indicates an access/rights related failure. + .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index 7e99a5ab6921..8d700eeb8b96 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -959,14 +959,14 @@ async def test_async_queue_schedule_message(self, servicebus_namespace_connectio async with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: - enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + scheduled_enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: async with sb_client.get_queue_sender(servicebus_queue.name) as sender: content = str(uuid.uuid4()) message_id = uuid.uuid4() message = Message(content) message.message_id = message_id - message.scheduled_enqueue_time_utc = enqueue_time + message.scheduled_enqueue_time_utc = scheduled_enqueue_time await sender.send_messages(message) messages = await receiver.receive_messages(max_wait_time=120) @@ -975,8 +975,8 @@ async def test_async_queue_schedule_message(self, servicebus_namespace_connectio data = str(messages[0]) assert data == content assert messages[0].message_id == message_id - assert messages[0].scheduled_enqueue_time_utc == enqueue_time - assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert messages[0].scheduled_enqueue_time_utc == scheduled_enqueue_time + assert messages[0].scheduled_enqueue_time_utc <= messages[0].enqueued_time_utc.replace(microsecond=0) assert len(messages) == 1 finally: for m in messages: @@ -992,7 +992,7 @@ async def test_async_queue_schedule_message(self, servicebus_namespace_connectio async def test_async_queue_schedule_multiple_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): async with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: - enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + scheduled_enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) messages = [] receiver = sb_client.get_queue_receiver(servicebus_queue.name, prefetch=20) sender = sb_client.get_queue_sender(servicebus_queue.name) @@ -1007,11 +1007,12 @@ async def test_async_queue_schedule_multiple_messages(self, servicebus_namespace await sender.send_messages([message_a, message_b]) - received_messages = await receiver.receive_messages(max_batch_size=2, max_wait_time=5) - for message in received_messages: + received_messages = [] + async for message in receiver.get_streaming_message_iter(max_wait_time=5): + received_messages.append(message) await message.complete() - tokens = await sender.schedule_messages(received_messages, enqueue_time) + tokens = await sender.schedule_messages(received_messages, scheduled_enqueue_time) assert len(tokens) == 2 messages = await receiver.receive_messages(max_wait_time=120) @@ -1022,8 +1023,8 @@ async def test_async_queue_schedule_multiple_messages(self, servicebus_namespace data = str(messages[0]) assert data == content assert messages[0].message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time_utc == enqueue_time - assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert messages[0].scheduled_enqueue_time_utc == scheduled_enqueue_time + assert messages[0].scheduled_enqueue_time_utc <= messages[0].enqueued_time_utc.replace(microsecond=0) assert len(messages) == 2 finally: for m in messages: @@ -1252,7 +1253,11 @@ def message_content(): message_1st_received_cnt = 0 message_2nd_received_cnt = 0 while message_1st_received_cnt < 20 or message_2nd_received_cnt < 20: - messages = await receiver.receive_messages(max_batch_size=20, max_wait_time=5) + messages = [] + batch = await receiver.receive_messages(max_batch_size=20, max_wait_time=5) + while batch: + messages += batch + batch = await receiver.receive_messages(max_batch_size=20, max_wait_time=5) if not messages: break receive_counter += 1 @@ -1382,22 +1387,22 @@ async def test_async_queue_receiver_respects_max_wait_time_overrides(self, servi async for message in receiver.get_streaming_message_iter(max_wait_time=1): messages.append(message) time_3 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=.5) < timedelta(milliseconds=(time_3 - time_2)) < timedelta(seconds=2) + assert timedelta(seconds=.5) < timedelta(milliseconds=(time_3 - time_2)) <= timedelta(seconds=2) time_4 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=8) < timedelta(milliseconds=(time_4 - time_3)) < timedelta(seconds=11) + assert timedelta(seconds=8) < timedelta(milliseconds=(time_4 - time_3)) <= timedelta(seconds=11) async for message in receiver.get_streaming_message_iter(max_wait_time=3): messages.append(message) time_5 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=1) < timedelta(milliseconds=(time_5 - time_4)) < timedelta(seconds=4) + assert timedelta(seconds=1) < timedelta(milliseconds=(time_5 - time_4)) <= timedelta(seconds=4) async for message in receiver: messages.append(message) time_6 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=3) < timedelta(milliseconds=(time_6 - time_5)) < timedelta(seconds=6) + assert timedelta(seconds=3) < timedelta(milliseconds=(time_6 - time_5)) <= timedelta(seconds=6) async for message in receiver.get_streaming_message_iter(): messages.append(message) time_7 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=3) < timedelta(milliseconds=(time_7 - time_6)) < timedelta(seconds=6) + assert timedelta(seconds=3) < timedelta(milliseconds=(time_7 - time_6)) <= timedelta(seconds=6) assert len(messages) == 1 diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py new file mode 100644 index 000000000000..351d1fe51a99 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py @@ -0,0 +1,60 @@ +#-------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +#-------------------------------------------------------------------------- + + +import logging +import pytest + +from azure.servicebus.aio import ServiceBusClient +from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer +from servicebus_preparer import CachedServiceBusNamespacePreparer, CachedServiceBusQueuePreparer +from utilities import get_logger + +_logger = get_logger(logging.DEBUG) + + +class ServiceBusClientAsyncTests(AzureMgmtTestCase): + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer() + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + async def test_async_sb_client_close_spawned_handlers(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string) + + await client.close() + + # context manager + async with client: + assert len(client._handlers) == 0 + sender = client.get_queue_sender(servicebus_queue.name) + receiver = client.get_queue_receiver(servicebus_queue.name) + await sender._open() + await receiver._open() + + assert sender._handler and sender._running + assert receiver._handler and receiver._running + assert len(client._handlers) == 2 + + assert not sender._handler and not sender._running + assert not receiver._handler and not receiver._running + assert len(client._handlers) == 0 + + # close operation + sender = client.get_queue_sender(servicebus_queue.name) + receiver = client.get_queue_receiver(servicebus_queue.name) + await sender._open() + await receiver._open() + + assert sender._handler and sender._running + assert receiver._handler and receiver._running + assert len(client._handlers) == 2 + + await client.close() + + assert not sender._handler and not sender._running + assert not receiver._handler and not receiver._running + assert len(client._handlers) == 0 diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index e41c7d14a24b..30e5b3c02082 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -1199,14 +1199,14 @@ def test_queue_schedule_message(self, servicebus_namespace_connection_string, se with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: - enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + scheduled_enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: with sb_client.get_queue_sender(servicebus_queue.name) as sender: content = str(uuid.uuid4()) message_id = uuid.uuid4() message = Message(content) message.message_id = message_id - message.scheduled_enqueue_time_utc = enqueue_time + message.scheduled_enqueue_time_utc = scheduled_enqueue_time sender.send_messages(message) messages = receiver.receive_messages(max_wait_time=120) @@ -1215,8 +1215,8 @@ def test_queue_schedule_message(self, servicebus_namespace_connection_string, se data = str(messages[0]) assert data == content assert messages[0].message_id == message_id - assert messages[0].scheduled_enqueue_time_utc == enqueue_time - assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert messages[0].scheduled_enqueue_time_utc == scheduled_enqueue_time + assert messages[0].scheduled_enqueue_time_utc <= messages[0].enqueued_time_utc.replace(microsecond=0) assert len(messages) == 1 finally: for m in messages: @@ -1235,7 +1235,7 @@ def test_queue_schedule_multiple_messages(self, servicebus_namespace_connection_ with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: - enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + scheduled_enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) sender = sb_client.get_queue_sender(servicebus_queue.name) receiver = sb_client.get_queue_receiver(servicebus_queue.name, prefetch=20) @@ -1265,7 +1265,7 @@ def test_queue_schedule_multiple_messages(self, servicebus_namespace_connection_ received_messages.append(message) message.complete() - tokens = sender.schedule_messages(received_messages, enqueue_time) + tokens = sender.schedule_messages(received_messages, scheduled_enqueue_time) assert len(tokens) == 2 messages = receiver.receive_messages(max_wait_time=120) @@ -1275,8 +1275,8 @@ def test_queue_schedule_multiple_messages(self, servicebus_namespace_connection_ data = str(messages[0]) assert data == content assert messages[0].message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time_utc == enqueue_time - assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert messages[0].scheduled_enqueue_time_utc == scheduled_enqueue_time + assert messages[0].scheduled_enqueue_time_utc <= messages[0].enqueued_time_utc.replace(microsecond=0) assert messages[0].delivery_count == 0 assert messages[0].properties assert messages[0].properties[b'key'] == b'value' @@ -1619,7 +1619,9 @@ def message_content(): message_1st_received_cnt = 0 message_2nd_received_cnt = 0 while message_1st_received_cnt < 20 or message_2nd_received_cnt < 20: - messages = receiver.receive_messages(max_batch_size=20, max_wait_time=5) + messages = [] + for message in receiver.get_streaming_message_iter(max_wait_time=5): + messages.append(message) if not messages: break receive_counter += 1 @@ -1788,22 +1790,38 @@ def test_queue_receiver_respects_max_wait_time_overrides(self, servicebus_namesp for message in receiver.get_streaming_message_iter(max_wait_time=1): messages.append(message) time_3 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=.5) < timedelta(milliseconds=(time_3 - time_2)) < timedelta(seconds=2) + assert timedelta(seconds=.5) < timedelta(milliseconds=(time_3 - time_2)) <= timedelta(seconds=2) time_4 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=8) < timedelta(milliseconds=(time_4 - time_3)) < timedelta(seconds=11) + assert timedelta(seconds=8) < timedelta(milliseconds=(time_4 - time_3)) <= timedelta(seconds=11) for message in receiver.get_streaming_message_iter(max_wait_time=3): messages.append(message) time_5 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=1) < timedelta(milliseconds=(time_5 - time_4)) < timedelta(seconds=4) + assert timedelta(seconds=1) < timedelta(milliseconds=(time_5 - time_4)) <= timedelta(seconds=4) for message in receiver: messages.append(message) time_6 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=3) < timedelta(milliseconds=(time_6 - time_5)) < timedelta(seconds=6) + assert timedelta(seconds=3) < timedelta(milliseconds=(time_6 - time_5)) <= timedelta(seconds=6) for message in receiver.get_streaming_message_iter(): messages.append(message) time_7 = receiver._handler._counter.get_current_ms() - assert timedelta(seconds=3) < timedelta(milliseconds=(time_7 - time_6)) < timedelta(seconds=6) + assert timedelta(seconds=3) < timedelta(milliseconds=(time_7 - time_6)) <= timedelta(seconds=6) assert len(messages) == 1 + + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest') + def test_queue_receiver_invalid_mode(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: +# with pytest.raises(StopIteration): + with sb_client.get_queue_receiver(servicebus_queue.name, + max_wait_time="oij") as receiver: + + assert receiver diff --git a/sdk/servicebus/azure-servicebus/tests/test_sb_client.py b/sdk/servicebus/azure-servicebus/tests/test_sb_client.py index 8f69482f0c4f..4d4cabf7638a 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_sb_client.py +++ b/sdk/servicebus/azure-servicebus/tests/test_sb_client.py @@ -13,13 +13,14 @@ from azure.common import AzureHttpError, AzureConflictHttpError from azure.mgmt.servicebus.models import AccessRights -from azure.servicebus import ServiceBusClient, ServiceBusSharedKeyCredential +from azure.servicebus import ServiceBusClient, ServiceBusSharedKeyCredential, ServiceBusSender from azure.servicebus._common.message import Message, PeekMessage from azure.servicebus._common.constants import ReceiveSettleMode from azure.servicebus.exceptions import ( ServiceBusError, ServiceBusConnectionError, ServiceBusAuthenticationError, + ServiceBusAuthorizationError, ServiceBusResourceNotFound ) from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer @@ -28,7 +29,8 @@ ServiceBusTopicPreparer, ServiceBusQueuePreparer, ServiceBusNamespaceAuthorizationRulePreparer, - ServiceBusQueueAuthorizationRulePreparer + ServiceBusQueueAuthorizationRulePreparer, + CachedServiceBusQueuePreparer ) class ServiceBusClientTests(AzureMgmtTestCase): @@ -44,7 +46,7 @@ def test_sb_client_bad_credentials(self, servicebus_namespace, servicebus_queue, credential=ServiceBusSharedKeyCredential('invalid', 'invalid'), logging_enable=False) with client: - with pytest.raises(ServiceBusError): + with pytest.raises(ServiceBusAuthenticationError): with client.get_queue_sender(servicebus_queue.name) as sender: sender.send_messages(Message("test")) @@ -87,7 +89,7 @@ def test_sb_client_readonly_credentials(self, servicebus_authorization_rule_conn with client.get_queue_receiver(servicebus_queue.name) as receiver: messages = receiver.receive_messages(max_batch_size=1, max_wait_time=1) - with pytest.raises(ServiceBusError): + with pytest.raises(ServiceBusAuthorizationError): with client.get_queue_sender(servicebus_queue.name) as sender: sender.send_messages(Message("test")) @@ -119,10 +121,71 @@ def test_sb_client_writeonly_credentials(self, servicebus_authorization_rule_con @ServiceBusQueuePreparer(name_prefix='servicebustest_qone', parameter_name='wrong_queue', dead_lettering_on_message_expiration=True) @ServiceBusQueuePreparer(name_prefix='servicebustest_qtwo', dead_lettering_on_message_expiration=True) @ServiceBusQueueAuthorizationRulePreparer(name_prefix='servicebustest_qtwo') - def test_sb_client_incorrect_queue_conn_str(self, servicebus_queue_authorization_rule_connection_string, wrong_queue, **kwargs): + def test_sb_client_incorrect_queue_conn_str(self, servicebus_queue_authorization_rule_connection_string, servicebus_queue, wrong_queue, **kwargs): client = ServiceBusClient.from_connection_string(servicebus_queue_authorization_rule_connection_string) with client: - with pytest.raises(ServiceBusError): + # Validate that the wrong queue with the right credentials fails. + with pytest.raises(ServiceBusAuthenticationError): with client.get_queue_sender(wrong_queue.name) as sender: sender.send_messages(Message("test")) + + # But that the correct one works. + with client.get_queue_sender(servicebus_queue.name) as sender: + sender.send_messages(Message("test")) + + # Now do the same but with direct connstr initialization. + with pytest.raises(ServiceBusAuthenticationError): + with ServiceBusSender.from_connection_string( + servicebus_queue_authorization_rule_connection_string, + queue_name=wrong_queue.name, + ) as sender: + sender.send_messages(Message("test")) + + with ServiceBusSender.from_connection_string( + servicebus_queue_authorization_rule_connection_string, + queue_name=servicebus_queue.name, + ) as sender: + sender.send_messages(Message("test")) + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer() + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + def test_sb_client_close_spawned_handlers(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string) + + client.close() + + # context manager + with client: + assert len(client._handlers) == 0 + sender = client.get_queue_sender(servicebus_queue.name) + receiver = client.get_queue_receiver(servicebus_queue.name) + sender._open() + receiver._open() + + assert sender._handler and sender._running + assert receiver._handler and receiver._running + assert len(client._handlers) == 2 + + assert not sender._handler and not sender._running + assert not receiver._handler and not receiver._running + assert len(client._handlers) == 0 + + # close operation + sender = client.get_queue_sender(servicebus_queue.name) + receiver = client.get_queue_receiver(servicebus_queue.name) + sender._open() + receiver._open() + + assert sender._handler and sender._running + assert receiver._handler and receiver._running + assert len(client._handlers) == 2 + + client.close() + + assert not sender._handler and not sender._running + assert not receiver._handler and not receiver._running + assert len(client._handlers) == 0 diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_client.py index f78cf7319140..85837199921b 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_client.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_client.py @@ -177,6 +177,19 @@ def _format_url(self, hostname): quote(self.blob_name, safe='~/'), self._query_str) + def _encode_source_url(self, source_url): + parsed_source_url = urlparse(source_url) + source_scheme = parsed_source_url.scheme + source_hostname = parsed_source_url.netloc.rstrip('/') + source_path = unquote(parsed_source_url.path) + source_query = parsed_source_url.query + return "{}://{}{}?{}".format( + source_scheme, + source_hostname, + quote(source_path, safe='~/'), + source_query + ) + @classmethod def from_blob_url(cls, blob_url, credential=None, snapshot=None, **kwargs): # type: (str, Optional[Any], Optional[Union[str, Dict[str, Any]]], Any) -> BlobClient @@ -1705,7 +1718,7 @@ def start_copy_from_url(self, source_url, metadata=None, incremental_copy=False, :caption: Copy a blob from a URL. """ options = self._start_copy_from_url_options( - source_url, + source_url=self._encode_source_url(source_url), metadata=metadata, incremental_copy=incremental_copy, **kwargs) @@ -2069,7 +2082,7 @@ def stage_block_from_url( """ options = self._stage_block_from_url_options( block_id, - source_url, + source_url=self._encode_source_url(source_url), source_offset=source_offset, source_length=source_length, source_content_md5=source_content_md5, @@ -3045,7 +3058,7 @@ def upload_pages_from_url(self, source_url, # type: str The timeout parameter is expressed in seconds. """ options = self._upload_pages_from_url_options( - source_url=source_url, + source_url=self._encode_source_url(source_url), offset=offset, length=length, source_offset=source_offset, @@ -3456,7 +3469,7 @@ def append_block_from_url(self, copy_source_url, # type: str The timeout parameter is expressed in seconds. """ options = self._append_block_from_url_options( - copy_source_url, + copy_source_url=self._encode_source_url(copy_source_url), source_offset=source_offset, source_length=source_length, **kwargs diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_client_async.py b/sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_client_async.py index edf6e2dfc468..d88075ae87b9 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_client_async.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_client_async.py @@ -1037,7 +1037,7 @@ async def start_copy_from_url(self, source_url, metadata=None, incremental_copy= :caption: Copy a blob from a URL. """ options = self._start_copy_from_url_options( - source_url, + source_url=self._encode_source_url(source_url), metadata=metadata, incremental_copy=incremental_copy, **kwargs) @@ -1287,7 +1287,7 @@ async def stage_block_from_url( """ options = self._stage_block_from_url_options( block_id, - source_url, + source_url=self._encode_source_url(source_url), source_offset=source_offset, source_length=source_length, source_content_md5=source_content_md5, @@ -1991,7 +1991,7 @@ async def upload_pages_from_url(self, source_url, # type: str """ options = self._upload_pages_from_url_options( - source_url=source_url, + source_url=self._encode_source_url(source_url), offset=offset, length=length, source_offset=source_offset, @@ -2250,7 +2250,7 @@ async def append_block_from_url(self, copy_source_url, # type: str The timeout parameter is expressed in seconds. """ options = self._append_block_from_url_options( - copy_source_url, + copy_source_url=self._encode_source_url(copy_source_url), source_offset=source_offset, source_length=source_length, **kwargs diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_async.test_copy_blob_async.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_async.test_copy_blob_async.yaml new file mode 100644 index 000000000000..27e1606ea252 --- /dev/null +++ b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_async.test_copy_blob_async.yaml @@ -0,0 +1,134 @@ +interactions: +- request: + body: null + headers: + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Wed, 26 Aug 2020 05:19:30 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer70e51129?restype=container + response: + body: + string: '' + headers: + content-length: '0' + date: Wed, 26 Aug 2020 05:19:30 GMT + etag: '"0x8D8497F9C8C44A7"' + last-modified: Wed, 26 Aug 2020 05:19:30 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-version: '2019-12-12' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer70e51129?restype=container +- request: + body: null + headers: + Content-Length: + - '0' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Wed, 26 Aug 2020 05:19:31 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer70e51129/blob70e51129 + response: + body: + string: '' + headers: + content-length: '0' + content-md5: 1B2M2Y8AsgTpgAmY7PhCfg== + date: Wed, 26 Aug 2020 05:19:31 GMT + etag: '"0x8D8497F9CAD5065"' + last-modified: Wed, 26 Aug 2020 05:19:31 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: AAAAAAAAAAA= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + x-ms-version-id: '2020-08-26T05:19:31.1203429Z' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer70e51129/blob70e51129 +- request: + body: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + Content-Length: + - '8192' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Wed, 26 Aug 2020 05:19:31 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer70e51129/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob70e51129 + response: + body: + string: '' + headers: + content-length: '0' + content-md5: IhmUBAsUKUvff7wSjmZjPA== + date: Wed, 26 Aug 2020 05:19:31 GMT + etag: '"0x8D8497F9CC0DC00"' + last-modified: Wed, 26 Aug 2020 05:19:31 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: ERTjv26IbjE= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + x-ms-version-id: '2020-08-26T05:19:31.2484352Z' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer70e51129/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob70e51129 +- request: + body: null + headers: + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainer70e51129/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob70e51129?se=2020-08-26T06%3A19%3A31Z&sp=rt&sv=2019-12-12&sr=b&sig=5VNeVcGYJjpvt4L69m5afdCQ88Mq/DBf8zwPuuaqego%3D + x-ms-date: + - Wed, 26 Aug 2020 05:19:31 GMT + x-ms-requires-sync: + - 'True' + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer70e51129/blob70e51129 + response: + body: + string: '' + headers: + content-length: '0' + date: Wed, 26 Aug 2020 05:19:31 GMT + etag: '"0x8D8497F9CF32F7E"' + last-modified: Wed, 26 Aug 2020 05:19:31 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: ERTjv26IbjE= + x-ms-copy-id: 5715bd25-f3ce-4c0b-a7a8-c8a687ee1131 + x-ms-copy-status: success + x-ms-version: '2019-12-12' + x-ms-version-id: '2020-08-26T05:19:31.5836748Z' + status: + code: 202 + message: Accepted + url: https://tamerdevtest.blob.core.windows.net/utcontainer70e51129/blob70e51129 +version: 1 diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_async.test_put_block_from_url_and_commit.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_async.test_put_block_from_url_and_commit.yaml new file mode 100644 index 000000000000..4251c50562ad --- /dev/null +++ b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_async.test_put_block_from_url_and_commit.yaml @@ -0,0 +1,257 @@ +interactions: +- request: + body: null + headers: + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Wed, 26 Aug 2020 15:45:04 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5?restype=container + response: + body: + string: '' + headers: + content-length: '0' + date: Wed, 26 Aug 2020 15:45:03 GMT + etag: '"0x8D849D70012873C"' + last-modified: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-version: '2019-12-12' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5?restype=container +- request: + body: null + headers: + Content-Length: + - '0' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Wed, 26 Aug 2020 15:45:04 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5 + response: + body: + string: '' + headers: + content-length: '0' + content-md5: 1B2M2Y8AsgTpgAmY7PhCfg== + date: Wed, 26 Aug 2020 15:45:04 GMT + etag: '"0x8D849D7002B2AD7"' + last-modified: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: AAAAAAAAAAA= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + x-ms-version-id: '2020-08-26T15:45:04.2560494Z' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5 +- request: + body: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + Content-Length: + - '8192' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Wed, 26 Aug 2020 15:45:04 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob8d1b16f5 + response: + body: + string: '' + headers: + content-length: '0' + content-md5: IhmUBAsUKUvff7wSjmZjPA== + date: Wed, 26 Aug 2020 15:45:04 GMT + etag: '"0x8D849D7003F0493"' + last-modified: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: ERTjv26IbjE= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + x-ms-version-id: '2020-08-26T15:45:04.3851411Z' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob8d1b16f5 +- request: + body: null + headers: + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob8d1b16f5?se=2020-08-26T16%3A45%3A04Z&sp=rt&sv=2019-12-12&sr=b&sig=nSFk0qXAMw7AFRBRwYhbJ2H6BV0FEqppjDwCQhE2Wyc%3D + x-ms-date: + - Wed, 26 Aug 2020 15:45:04 GMT + x-ms-source-range: + - bytes=0-4095 + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blockid=MQ%3D%3D&comp=block + response: + body: + string: '' + headers: + content-length: '0' + date: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: Ep3PX5ZZvPI= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blockid=MQ%3D%3D&comp=block +- request: + body: null + headers: + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob8d1b16f5?se=2020-08-26T16%3A45%3A04Z&sp=rt&sv=2019-12-12&sr=b&sig=nSFk0qXAMw7AFRBRwYhbJ2H6BV0FEqppjDwCQhE2Wyc%3D + x-ms-date: + - Wed, 26 Aug 2020 15:45:05 GMT + x-ms-source-range: + - bytes=4096-8191 + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blockid=Mg%3D%3D&comp=block + response: + body: + string: '' + headers: + content-length: '0' + date: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: Ep3PX5ZZvPI= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blockid=Mg%3D%3D&comp=block +- request: + body: null + headers: + Accept: + - application/xml + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Wed, 26 Aug 2020 15:45:05 GMT + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blocklisttype=all&comp=blocklist + response: + body: + string: "\uFEFFMQ==4096Mg==4096" + headers: + content-type: application/xml + date: Wed, 26 Aug 2020 15:45:04 GMT + etag: '"0x8D849D7002B2AD7"' + last-modified: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-ms-blob-content-length: '0' + x-ms-version: '2019-12-12' + status: + code: 200 + message: OK + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blocklisttype=all&comp=blocklist +- request: + body: ' + + MQ==Mg==' + headers: + Content-Length: + - '104' + Content-Type: + - application/xml; charset=utf-8 + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Wed, 26 Aug 2020 15:45:05 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?comp=blocklist + response: + body: + string: '' + headers: + content-length: '0' + date: Wed, 26 Aug 2020 15:45:04 GMT + etag: '"0x8D849D700843076"' + last-modified: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: jBoHqXt/R3g= + x-ms-request-server-encrypted: 'true' + x-ms-version: '2019-12-12' + x-ms-version-id: '2020-08-26T15:45:04.8394630Z' + status: + code: 201 + message: Created + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?comp=blocklist +- request: + body: null + headers: + Accept: + - application/xml + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Wed, 26 Aug 2020 15:45:05 GMT + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blocklisttype=all&comp=blocklist + response: + body: + string: "\uFEFFMQ==4096Mg==4096" + headers: + content-type: application/xml + date: Wed, 26 Aug 2020 15:45:04 GMT + etag: '"0x8D849D700843076"' + last-modified: Wed, 26 Aug 2020 15:45:04 GMT + server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-ms-blob-content-length: '8192' + x-ms-version: '2019-12-12' + status: + code: 200 + message: OK + url: https://tamerdevtest.blob.core.windows.net/utcontainer8d1b16f5/blob8d1b16f5?blocklisttype=all&comp=blocklist +version: 1 diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_copy_blob_sync.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_copy_blob_sync.yaml index 63296199260f..5570d1f0e420 100644 --- a/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_copy_blob_sync.yaml +++ b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_copy_blob_sync.yaml @@ -11,11 +11,11 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:20 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainera9621281?restype=container response: @@ -25,15 +25,15 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:43:26 GMT + - Sat, 22 Aug 2020 22:33:18 GMT etag: - - '"0x8D7597B395B9E6A"' + - '"0x8D846EB5EEB248E"' last-modified: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:19 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -53,13 +53,13 @@ interactions: If-None-Match: - '*' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-blob-type: - BlockBlob x-ms-date: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:21 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainera9621281/srcbloba9621281 response: @@ -71,11 +71,11 @@ interactions: content-md5: - IhmUBAsUKUvff7wSjmZjPA== date: - - Fri, 25 Oct 2019 18:43:26 GMT + - Sat, 22 Aug 2020 22:33:19 GMT etag: - - '"0x8D7597B3964F641"' + - '"0x8D846EB5EFF16BC"' last-modified: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:19 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-content-crc64: @@ -83,10 +83,184 @@ interactions: x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:33:19.7959868Z' status: code: 201 message: Created +- request: + body: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '8192' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Sat, 22 Aug 2020 22:33:21 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainera9621281/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86bloba9621281 + response: + body: + string: '' + headers: + content-length: + - '0' + content-md5: + - IhmUBAsUKUvff7wSjmZjPA== + date: + - Sat, 22 Aug 2020 22:33:19 GMT + etag: + - '"0x8D846EB5F1365B4"' + last-modified: + - Sat, 22 Aug 2020 22:33:19 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - ERTjv26IbjE= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:33:19.9290804Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainera9621281/srcbloba9621281?se=2020-08-22T23%3A33%3A21Z&sp=rt&sv=2019-12-12&sr=b&sig=yWlDAXwjK/kWC3IkFPDe8%2BNXNE7sXLcUUpmLNCeAN2k%3D + x-ms-date: + - Sat, 22 Aug 2020 22:33:21 GMT + x-ms-requires-sync: + - 'True' + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainera9621281/destbloba9621281 + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Sat, 22 Aug 2020 22:33:19 GMT + etag: + - '"0x8D846EB5F40A70B"' + last-modified: + - Sat, 22 Aug 2020 22:33:20 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - ERTjv26IbjE= + x-ms-copy-id: + - b3d72599-0236-4c0b-bc8e-afc5adfd7898 + x-ms-copy-status: + - success + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:33:20.2282924Z' + status: + code: 202 + message: Accepted +- request: + body: null + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 22:33:21 GMT + x-ms-range: + - bytes=0-33554431 + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainera9621281/destbloba9621281 + response: + body: + string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + accept-ranges: + - bytes + content-length: + - '8192' + content-range: + - bytes 0-8191/8192 + content-type: + - application/octet-stream + date: + - Sat, 22 Aug 2020 22:33:19 GMT + etag: + - '"0x8D846EB5F40A70B"' + last-modified: + - Sat, 22 Aug 2020 22:33:20 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-content-md5: + - IhmUBAsUKUvff7wSjmZjPA== + x-ms-blob-type: + - BlockBlob + x-ms-copy-completion-time: + - Sat, 22 Aug 2020 22:33:20 GMT + x-ms-copy-id: + - b3d72599-0236-4c0b-bc8e-afc5adfd7898 + x-ms-copy-progress: + - 8192/8192 + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainera9621281/srcbloba9621281?se=2020-08-22T23%3A33%3A21Z&sp=rt&sv=2019-12-12&sr=b + x-ms-copy-status: + - success + x-ms-creation-time: + - Sat, 22 Aug 2020 22:33:20 GMT + x-ms-is-current-version: + - 'true' + x-ms-lease-state: + - available + x-ms-lease-status: + - unlocked + x-ms-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:33:20.2282924Z' + status: + code: 206 + message: Partial Content - request: body: null headers: @@ -99,15 +273,15 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-copy-source: - - https://pyacrstoragec3mt3efpmwli.blob.core.windows.net/utcontainera9621281/srcbloba9621281?se=2019-10-25T19%3A43%3A27Z&sp=r&sv=2019-02-02&sr=b&sig=hlXcN9dWXC0HvyNSVzABnGDxS02q%2BU7xUe7mFnWjc3U%3D + - https://tamerdevtest.blob.core.windows.net/utcontainera9621281/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86bloba9621281?se=2020-08-22T23%3A33%3A21Z&sp=rt&sv=2019-12-12&sr=b&sig=OkT%2B1FCMdpMN4pAmakFfgc2%2BEWqGzU0fWrl/042SV1M%3D x-ms-date: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:21 GMT x-ms-requires-sync: - 'True' x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainera9621281/destbloba9621281 response: @@ -117,21 +291,23 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:19 GMT etag: - - '"0x8D7597B39D2239C"' + - '"0x8D846EB5F69E15C"' last-modified: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:33:20 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-content-crc64: - ERTjv26IbjE= x-ms-copy-id: - - b2f119a9-b94c-4f34-99d7-1101698efbd1 + - c9d62e9a-69f0-48f6-8c38-4fb834db20ab x-ms-copy-status: - success x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:33:20.4974819Z' status: code: 202 message: Accepted @@ -145,13 +321,13 @@ interactions: Connection: - keep-alive User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:33:21 GMT x-ms-range: - bytes=0-33554431 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: GET uri: https://storagename.blob.core.windows.net/utcontainera9621281/destbloba9621281 response: @@ -167,11 +343,11 @@ interactions: content-type: - application/octet-stream date: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:33:19 GMT etag: - - '"0x8D7597B39D2239C"' + - '"0x8D846EB5F69E15C"' last-modified: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:33:20 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-content-md5: @@ -179,17 +355,19 @@ interactions: x-ms-blob-type: - BlockBlob x-ms-copy-completion-time: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:33:20 GMT x-ms-copy-id: - - b2f119a9-b94c-4f34-99d7-1101698efbd1 + - c9d62e9a-69f0-48f6-8c38-4fb834db20ab x-ms-copy-progress: - 8192/8192 x-ms-copy-source: - - https://pyacrstoragec3mt3efpmwli.blob.core.windows.net/utcontainera9621281/srcbloba9621281?se=2019-10-25T19%3A43%3A27Z&sp=r&sv=2019-02-02&sr=b&sig=hlXcN9dWXC0HvyNSVzABnGDxS02q%2BU7xUe7mFnWjc3U%3D + - https://tamerdevtest.blob.core.windows.net/utcontainera9621281/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86bloba9621281?se=2020-08-22T23%3A33%3A21Z&sp=rt&sv=2019-12-12&sr=b x-ms-copy-status: - success x-ms-creation-time: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:33:20 GMT + x-ms-is-current-version: + - 'true' x-ms-lease-state: - available x-ms-lease-status: @@ -197,7 +375,9 @@ interactions: x-ms-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:33:20.4974819Z' status: code: 206 message: Partial Content diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_put_block_from_url_and_commit.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_put_block_from_url_and_commit.yaml index 774bdc43f532..76ec6ad22df0 100644 --- a/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_put_block_from_url_and_commit.yaml +++ b/sdk/storage/azure-storage-blob/tests/recordings/test_block_blob_sync_copy.test_put_block_from_url_and_commit.yaml @@ -11,11 +11,11 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:05:22 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae?restype=container response: @@ -25,15 +25,15 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:43:27 GMT + - Sat, 22 Aug 2020 22:05:21 GMT etag: - - '"0x8D7597B39FF894F"' + - '"0x8D846E776FF3690"' last-modified: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:05:22 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -53,13 +53,13 @@ interactions: If-None-Match: - '*' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-blob-type: - BlockBlob x-ms-date: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:05:23 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/srcblobf05f18ae response: @@ -71,11 +71,11 @@ interactions: content-md5: - IhmUBAsUKUvff7wSjmZjPA== date: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:05:21 GMT etag: - - '"0x8D7597B3A468BD1"' + - '"0x8D846E77716AEDE"' last-modified: - - Fri, 25 Oct 2019 18:43:28 GMT + - Sat, 22 Aug 2020 22:05:22 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-content-crc64: @@ -83,7 +83,61 @@ interactions: x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:05:22.2290142Z' + status: + code: 201 + message: Created +- request: + body: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '8192' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Sat, 22 Aug 2020 22:05:23 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blobf05f18ae + response: + body: + string: '' + headers: + content-length: + - '0' + content-md5: + - IhmUBAsUKUvff7wSjmZjPA== + date: + - Sat, 22 Aug 2020 22:05:21 GMT + etag: + - '"0x8D846E7772E80FB"' + last-modified: + - Sat, 22 Aug 2020 22:05:22 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - ERTjv26IbjE= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:05:22.3861261Z' status: code: 201 message: Created @@ -99,15 +153,15 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-copy-source: - - https://pyacrstoragec3mt3efpmwli.blob.core.windows.net/utcontainerf05f18ae/srcblobf05f18ae?se=2019-10-25T19%3A43%3A29Z&sp=r&sv=2019-02-02&sr=b&sig=pmp7GJORAWc/qr7iDlbbXynQb32xBuXKE4JR6pYy7xk%3D + - https://tamerdevtest.blob.core.windows.net/utcontainerf05f18ae/srcblobf05f18ae?se=2020-08-22T23%3A05%3A23Z&sp=rt&sv=2019-12-12&sr=b&sig=ur2OzABfFKhLGRRFnrkIkZzptQwkVuRUMewNriXJw5I%3D x-ms-date: - - Fri, 25 Oct 2019 18:43:29 GMT + - Sat, 22 Aug 2020 22:05:32 GMT x-ms-source-range: - bytes=0-4095 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?blockid=MQ%3D%3D&comp=block response: @@ -117,7 +171,7 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:43:29 GMT + - Sat, 22 Aug 2020 22:05:30 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-content-crc64: @@ -125,7 +179,7 @@ interactions: x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -141,15 +195,15 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-copy-source: - - https://pyacrstoragec3mt3efpmwli.blob.core.windows.net/utcontainerf05f18ae/srcblobf05f18ae?se=2019-10-25T19%3A43%3A29Z&sp=r&sv=2019-02-02&sr=b&sig=pmp7GJORAWc/qr7iDlbbXynQb32xBuXKE4JR6pYy7xk%3D + - https://tamerdevtest.blob.core.windows.net/utcontainerf05f18ae/srcblobf05f18ae?se=2020-08-22T23%3A05%3A23Z&sp=rt&sv=2019-12-12&sr=b&sig=ur2OzABfFKhLGRRFnrkIkZzptQwkVuRUMewNriXJw5I%3D x-ms-date: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:14 GMT x-ms-source-range: - bytes=4096-8191 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?blockid=Mg%3D%3D&comp=block response: @@ -159,7 +213,7 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:43:29 GMT + - Sat, 22 Aug 2020 22:06:12 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-content-crc64: @@ -167,7 +221,7 @@ interactions: x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -181,11 +235,11 @@ interactions: Connection: - keep-alive User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:14 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: GET uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?blocklisttype=all&comp=blocklist response: @@ -196,13 +250,13 @@ interactions: content-type: - application/xml date: - - Fri, 25 Oct 2019 18:43:29 GMT + - Sat, 22 Aug 2020 22:06:12 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 transfer-encoding: - chunked x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 200 message: OK @@ -222,11 +276,11 @@ interactions: Content-Type: - application/xml; charset=utf-8 User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:14 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?comp=blocklist response: @@ -236,11 +290,11 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:43:29 GMT + - Sat, 22 Aug 2020 22:06:12 GMT etag: - - '"0x8D7597B3AF1E953"' + - '"0x8D846E795A2164F"' last-modified: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:13 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-content-crc64: @@ -248,7 +302,239 @@ interactions: x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:06:13.4742607Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 22:06:14 GMT + x-ms-range: + - bytes=0-33554431 + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae + response: + body: + string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + accept-ranges: + - bytes + content-length: + - '8192' + content-range: + - bytes 0-8191/8192 + content-type: + - application/octet-stream + date: + - Sat, 22 Aug 2020 22:06:13 GMT + etag: + - '"0x8D846E795A2164F"' + last-modified: + - Sat, 22 Aug 2020 22:06:13 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-type: + - BlockBlob + x-ms-creation-time: + - Sat, 22 Aug 2020 22:06:13 GMT + x-ms-is-current-version: + - 'true' + x-ms-lease-state: + - available + x-ms-lease-status: + - unlocked + x-ms-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:06:13.4742607Z' + status: + code: 206 + message: Partial Content +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainerf05f18ae/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blobf05f18ae?se=2020-08-22T23%3A05%3A23Z&sp=rt&sv=2019-12-12&sr=b&sig=33UzXINjfLjuvsT/X5TQwCuruo1DR8xasVf49CrKU7w%3D + x-ms-date: + - Sat, 22 Aug 2020 22:06:47 GMT + x-ms-source-range: + - bytes=0-4095 + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?blockid=Mw%3D%3D&comp=block + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Sat, 22 Aug 2020 22:06:45 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - Ep3PX5ZZvPI= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainerf05f18ae/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blobf05f18ae?se=2020-08-22T23%3A05%3A23Z&sp=rt&sv=2019-12-12&sr=b&sig=33UzXINjfLjuvsT/X5TQwCuruo1DR8xasVf49CrKU7w%3D + x-ms-date: + - Sat, 22 Aug 2020 22:06:48 GMT + x-ms-source-range: + - bytes=4096-8191 + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?blockid=NA%3D%3D&comp=block + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Sat, 22 Aug 2020 22:06:46 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - Ep3PX5ZZvPI= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 22:06:48 GMT + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?blocklisttype=all&comp=blocklist + response: + body: + string: "\uFEFFMQ==4096Mg==4096Mw==4096NA==4096" + headers: + content-type: + - application/xml + date: + - Sat, 22 Aug 2020 22:06:46 GMT + etag: + - '"0x8D846E795A2164F"' + last-modified: + - Sat, 22 Aug 2020 22:06:13 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-ms-blob-content-length: + - '8192' + x-ms-version: + - '2019-12-12' + status: + code: 200 + message: OK +- request: + body: ' + + Mw==NA==' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '104' + Content-Type: + - application/xml; charset=utf-8 + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 22:06:48 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae?comp=blocklist + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Sat, 22 Aug 2020 22:06:46 GMT + etag: + - '"0x8D846E7A9F30FF1"' + last-modified: + - Sat, 22 Aug 2020 22:06:47 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - NtnF99lsyrs= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:06:47.5603713Z' status: code: 201 message: Created @@ -262,13 +548,13 @@ interactions: Connection: - keep-alive User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:48 GMT x-ms-range: - bytes=0-33554431 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: GET uri: https://storagename.blob.core.windows.net/utcontainerf05f18ae/destblobf05f18ae response: @@ -284,17 +570,19 @@ interactions: content-type: - application/octet-stream date: - - Fri, 25 Oct 2019 18:43:29 GMT + - Sat, 22 Aug 2020 22:06:47 GMT etag: - - '"0x8D7597B3AF1E953"' + - '"0x8D846E7A9F30FF1"' last-modified: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:47 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-type: - BlockBlob x-ms-creation-time: - - Fri, 25 Oct 2019 18:43:30 GMT + - Sat, 22 Aug 2020 22:06:47 GMT + x-ms-is-current-version: + - 'true' x-ms-lease-state: - available x-ms-lease-status: @@ -302,7 +590,9 @@ interactions: x-ms-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T22:06:47.5603713Z' status: code: 206 message: Partial Content diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_page_blob.test_upload_pages_from_url.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_page_blob.test_upload_pages_from_url.yaml index f6099bc49a28..a73f46eb3e10 100644 --- a/sdk/storage/azure-storage-blob/tests/recordings/test_page_blob.test_upload_pages_from_url.yaml +++ b/sdk/storage/azure-storage-blob/tests/recordings/test_page_blob.test_upload_pages_from_url.yaml @@ -11,11 +11,11 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:20 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1?restype=container response: @@ -25,15 +25,15 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:18 GMT etag: - - '"0x8D759767EA8C83D"' + - '"0x8D846F6D35B46BA"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:19 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -49,11 +49,11 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:20 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainersource5dfe10c1?restype=container response: @@ -63,15 +63,15 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:19 GMT etag: - - '"0x8D759767EB10722"' + - '"0x8D846F6D3727C64"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:19 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -87,15 +87,15 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-blob-content-length: - '8192' x-ms-blob-type: - PageBlob x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainersource5dfe10c1/blob5dfe10c1 response: @@ -105,17 +105,19 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:19 GMT etag: - - '"0x8D759767EB9AEE7"' + - '"0x8D846F6D3893760"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:19 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:19.7809504Z' status: code: 201 message: Created @@ -133,15 +135,15 @@ interactions: Content-Type: - application/octet-stream User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT x-ms-page-write: - update x-ms-range: - bytes=0-8191 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainersource5dfe10c1/blob5dfe10c1?comp=page response: @@ -150,22 +152,22 @@ interactions: headers: content-length: - '0' + content-md5: + - IhmUBAsUKUvff7wSjmZjPA== date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:19 GMT etag: - - '"0x8D759767EC1C69F"' + - '"0x8D846F6D3A01EE5"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:19 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-sequence-number: - '0' - x-ms-content-crc64: - - ERTjv26IbjE= x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -181,15 +183,111 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-blob-content-length: - '8192' x-ms-blob-type: - PageBlob x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainersource5dfe10c1/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob5dfe10c1 + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Sat, 22 Aug 2020 23:55:19 GMT + etag: + - '"0x8D846F6D3B7C9E1"' + last-modified: + - Sat, 22 Aug 2020 23:55:20 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:20.0871673Z' + status: + code: 201 + message: Created +- request: + body: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '8192' + Content-Type: + - application/octet-stream + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 23:55:21 GMT + x-ms-page-write: + - update + x-ms-range: + - bytes=0-8191 + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainersource5dfe10c1/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob5dfe10c1?comp=page + response: + body: + string: '' + headers: + content-length: + - '0' + content-md5: + - IhmUBAsUKUvff7wSjmZjPA== + date: + - Sat, 22 Aug 2020 23:55:19 GMT + etag: + - '"0x8D846F6D3CF74DE"' + last-modified: + - Sat, 22 Aug 2020 23:55:20 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-sequence-number: + - '0' + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-content-length: + - '8192' + x-ms-blob-type: + - PageBlob + x-ms-date: + - Sat, 22 Aug 2020 23:55:21 GMT + x-ms-version: + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1 response: @@ -199,17 +297,19 @@ interactions: content-length: - '0' date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:19 GMT etag: - - '"0x8D759767EC9B743"' + - '"0x8D846F6D3E68376"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:20 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:20.3933837Z' status: code: 201 message: Created @@ -225,11 +325,11 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-copy-source: - - https://pyacrstoragedtrpf3xfnfdp.blob.core.windows.net/utcontainersource5dfe10c1/blob5dfe10c1?se=2019-10-25T19%3A09%3A36Z&sp=rd&sv=2019-02-02&sr=b&sig=5V5rE7JxXyu5zkiy1GeSqpxXicJjITU1vZ4RNt8HrfA%3D + - https://tamerdevtest.blob.core.windows.net/utcontainersource5dfe10c1/blob5dfe10c1?se=2020-08-23T00%3A55%3A21Z&sp=rdt&sv=2019-12-12&sr=b&sig=sUiZ2hFamJ6zGFddsNvmAbHp1d8GNLCcP5tOvWYHhQA%3D x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT x-ms-page-write: - update x-ms-range: @@ -237,7 +337,7 @@ interactions: x-ms-source-range: - bytes=0-4095 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1?comp=page response: @@ -246,22 +346,22 @@ interactions: headers: content-length: - '0' + content-md5: + - IaGZxT9CKjgOILFi+26+nA== date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:20 GMT etag: - - '"0x8D759767ED68AC3"' + - '"0x8D846F6D442BDE2"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:20 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-sequence-number: - '0' - x-ms-content-crc64: - - Ep3PX5ZZvPI= x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -277,11 +377,11 @@ interactions: Content-Length: - '0' User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-copy-source: - - https://pyacrstoragedtrpf3xfnfdp.blob.core.windows.net/utcontainersource5dfe10c1/blob5dfe10c1?se=2019-10-25T19%3A09%3A36Z&sp=rd&sv=2019-02-02&sr=b&sig=5V5rE7JxXyu5zkiy1GeSqpxXicJjITU1vZ4RNt8HrfA%3D + - https://tamerdevtest.blob.core.windows.net/utcontainersource5dfe10c1/blob5dfe10c1?se=2020-08-23T00%3A55%3A21Z&sp=rdt&sv=2019-12-12&sr=b&sig=sUiZ2hFamJ6zGFddsNvmAbHp1d8GNLCcP5tOvWYHhQA%3D x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:22 GMT x-ms-page-write: - update x-ms-range: @@ -289,7 +389,7 @@ interactions: x-ms-source-range: - bytes=4096-8191 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: PUT uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1?comp=page response: @@ -298,22 +398,22 @@ interactions: headers: content-length: - '0' + content-md5: + - IaGZxT9CKjgOILFi+26+nA== date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:20 GMT etag: - - '"0x8D759767EDEF096"' + - '"0x8D846F6D458E1F4"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-sequence-number: - '0' - x-ms-content-crc64: - - Ep3PX5ZZvPI= x-ms-request-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 201 message: Created @@ -327,11 +427,11 @@ interactions: Connection: - keep-alive User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:22 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: HEAD uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1 response: @@ -345,11 +445,11 @@ interactions: content-type: - application/octet-stream date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:20 GMT etag: - - '"0x8D759767EDEF096"' + - '"0x8D846F6D458E1F4"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-sequence-number: @@ -357,7 +457,9 @@ interactions: x-ms-blob-type: - PageBlob x-ms-creation-time: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:20 GMT + x-ms-is-current-version: + - 'true' x-ms-lease-state: - available x-ms-lease-status: @@ -365,7 +467,9 @@ interactions: x-ms-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:20.3933837Z' status: code: 200 message: OK @@ -379,13 +483,13 @@ interactions: Connection: - keep-alive User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:22 GMT x-ms-range: - bytes=0-33554431 x-ms-version: - - '2019-02-02' + - '2019-12-12' method: GET uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1 response: @@ -401,11 +505,11 @@ interactions: content-type: - application/octet-stream date: - - Fri, 25 Oct 2019 18:09:35 GMT + - Sat, 22 Aug 2020 23:55:20 GMT etag: - - '"0x8D759767EDEF096"' + - '"0x8D846F6D458E1F4"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:21 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 x-ms-blob-sequence-number: @@ -413,7 +517,217 @@ interactions: x-ms-blob-type: - PageBlob x-ms-creation-time: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:20 GMT + x-ms-is-current-version: + - 'true' + x-ms-lease-state: + - available + x-ms-lease-status: + - unlocked + x-ms-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:20.3933837Z' + status: + code: 206 + message: Partial Content +- request: + body: null + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 23:55:22 GMT + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1?comp=pagelist + response: + body: + string: "\uFEFF08191" + headers: + content-type: + - application/xml + date: + - Sat, 22 Aug 2020 23:55:21 GMT + etag: + - '"0x8D846F6D458E1F4"' + last-modified: + - Sat, 22 Aug 2020 23:55:21 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-ms-blob-content-length: + - '8192' + x-ms-version: + - '2019-12-12' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-copy-source: + - https://tamerdevtest.blob.core.windows.net/utcontainersource5dfe10c1/%E0%A4%AD%E0%A4%BE%E0%A4%B0%E0%A4%A4%C2%A5test/testsub%C3%90ir%C3%8D/src%C3%86blob5dfe10c1?se=2020-08-23T00%3A55%3A21Z&sp=rdt&sv=2019-12-12&sr=b&sig=aQAXAXPEcm%2BE%2BDLpjddg6tZ5xP8MrT5puW3CdWCQXks%3D + x-ms-date: + - Sat, 22 Aug 2020 23:55:23 GMT + x-ms-page-write: + - update + x-ms-range: + - bytes=0-4095 + x-ms-source-range: + - bytes=0-4095 + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1?comp=page + response: + body: + string: '' + headers: + content-length: + - '0' + content-md5: + - IaGZxT9CKjgOILFi+26+nA== + date: + - Sat, 22 Aug 2020 23:55:21 GMT + etag: + - '"0x8D846F6D4F5655A"' + last-modified: + - Sat, 22 Aug 2020 23:55:22 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-sequence-number: + - '0' + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 23:55:23 GMT + x-ms-version: + - '2019-12-12' + method: HEAD + uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1 + response: + body: + string: '' + headers: + accept-ranges: + - bytes + content-length: + - '8192' + content-type: + - application/octet-stream + date: + - Sat, 22 Aug 2020 23:55:21 GMT + etag: + - '"0x8D846F6D4F5655A"' + last-modified: + - Sat, 22 Aug 2020 23:55:22 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-sequence-number: + - '0' + x-ms-blob-type: + - PageBlob + x-ms-creation-time: + - Sat, 22 Aug 2020 23:55:20 GMT + x-ms-is-current-version: + - 'true' + x-ms-lease-state: + - available + x-ms-lease-status: + - unlocked + x-ms-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:20.3933837Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sat, 22 Aug 2020 23:55:23 GMT + x-ms-range: + - bytes=0-33554431 + x-ms-version: + - '2019-12-12' + method: GET + uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1 + response: + body: + string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + headers: + accept-ranges: + - bytes + content-length: + - '8192' + content-range: + - bytes 0-8191/8192 + content-type: + - application/octet-stream + date: + - Sat, 22 Aug 2020 23:55:21 GMT + etag: + - '"0x8D846F6D4F5655A"' + last-modified: + - Sat, 22 Aug 2020 23:55:22 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-sequence-number: + - '0' + x-ms-blob-type: + - PageBlob + x-ms-creation-time: + - Sat, 22 Aug 2020 23:55:20 GMT + x-ms-is-current-version: + - 'true' x-ms-lease-state: - available x-ms-lease-status: @@ -421,7 +735,9 @@ interactions: x-ms-server-encrypted: - 'true' x-ms-version: - - '2019-02-02' + - '2019-12-12' + x-ms-version-id: + - '2020-08-22T23:55:20.3933837Z' status: code: 206 message: Partial Content @@ -435,11 +751,11 @@ interactions: Connection: - keep-alive User-Agent: - - azsdk-python-storage-blob/12.0.0b5 Python/3.6.3 (Windows-10-10.0.18362-SP0) + - azsdk-python-storage-blob/12.4.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) x-ms-date: - - Fri, 25 Oct 2019 18:09:37 GMT + - Sat, 22 Aug 2020 23:55:23 GMT x-ms-version: - - '2019-02-02' + - '2019-12-12' method: GET uri: https://storagename.blob.core.windows.net/utcontainer5dfe10c1/blob5dfe10c1?comp=pagelist response: @@ -449,11 +765,11 @@ interactions: content-type: - application/xml date: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:22 GMT etag: - - '"0x8D759767EDEF096"' + - '"0x8D846F6D4F5655A"' last-modified: - - Fri, 25 Oct 2019 18:09:36 GMT + - Sat, 22 Aug 2020 23:55:22 GMT server: - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 transfer-encoding: @@ -461,7 +777,7 @@ interactions: x-ms-blob-content-length: - '8192' x-ms-version: - - '2019-02-02' + - '2019-12-12' status: code: 200 message: OK diff --git a/sdk/storage/azure-storage-blob/tests/test_block_blob_async.py b/sdk/storage/azure-storage-blob/tests/test_block_blob_async.py index 0b158529f338..bdfb2e5fc00c 100644 --- a/sdk/storage/azure-storage-blob/tests/test_block_blob_async.py +++ b/sdk/storage/azure-storage-blob/tests/test_block_blob_async.py @@ -11,6 +11,7 @@ import asyncio import uuid +from datetime import datetime, timedelta from azure.core.exceptions import HttpResponseError, ResourceExistsError, ResourceModifiedError from azure.core.pipeline.transport import AioHttpTransport from multidict import CIMultiDict, CIMultiDictProxy @@ -22,7 +23,9 @@ BlobType, ContentSettings, BlobBlock, - StandardBlobTier + StandardBlobTier, + generate_blob_sas, + BlobSasPermissions ) from azure.storage.blob.aio import ( @@ -77,6 +80,24 @@ def _teardown(self, FILE_PATH): def _get_blob_reference(self): return self.get_resource_name(TEST_BLOB_PREFIX) + def _get_blob_with_special_chars_reference(self): + return 'भारत¥test/testsubÐirÍ/'+self.get_resource_name('srcÆblob') + + async def _create_source_blob_url_with_special_chars(self, tags=None): + blob_name = self._get_blob_with_special_chars_reference() + blob = self.bsc.get_blob_client(self.container_name, blob_name) + await blob.upload_blob(self.get_random_bytes(8 * 1024)) + sas_token_for_special_chars = generate_blob_sas( + blob.account_name, + blob.container_name, + blob.blob_name, + snapshot=blob.snapshot, + account_key=blob.credential.account_key, + permission=BlobSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=1), + ) + return BlobClient.from_blob_url(blob.url, credential=sas_token_for_special_chars).url + async def _create_blob(self, tags=None): blob_name = self._get_blob_reference() blob = self.bsc.get_blob_client(self.container_name, blob_name) @@ -115,6 +136,50 @@ async def test_put_block(self, resource_group, location, storage_account, storag # Assert + @GlobalStorageAccountPreparer() + @AsyncStorageTestCase.await_prepared_test + async def test_copy_blob_async(self, resource_group, location, storage_account, storage_account_key): + await self._setup(storage_account, storage_account_key) + dest_blob = await self._create_blob() + source_blob_url = await self._create_source_blob_url_with_special_chars() + + # Act + copy_props = await dest_blob.start_copy_from_url(source_blob_url, requires_sync=True) + + # Assert + self.assertIsNotNone(copy_props) + self.assertIsNotNone(copy_props['copy_id']) + self.assertEqual('success', copy_props['copy_status']) + + @GlobalStorageAccountPreparer() + @AsyncStorageTestCase.await_prepared_test + async def test_put_block_from_url_and_commit(self, resource_group, location, storage_account, storage_account_key): + await self._setup(storage_account, storage_account_key) + dest_blob = await self._create_blob() + source_blob_url = await self._create_source_blob_url_with_special_chars() + split = 4 * 1024 + # Act part 1: make put block from url calls + await dest_blob.stage_block_from_url( + block_id=1, + source_url=source_blob_url, + source_offset=0, + source_length=split) + await dest_blob.stage_block_from_url( + block_id=2, + source_url=source_blob_url, + source_offset=split, + source_length=split) + + # Assert blocks + committed, uncommitted = await dest_blob.get_block_list('all') + self.assertEqual(len(uncommitted), 2) + self.assertEqual(len(committed), 0) + # Act part 2: commit the blocks + await dest_blob.commit_block_list(['1', '2']) + committed, uncommitted = await dest_blob.get_block_list('all') + self.assertEqual(len(uncommitted), 0) + self.assertEqual(len(committed), 2) + @GlobalStorageAccountPreparer() @AsyncStorageTestCase.await_prepared_test async def test_put_block_with_response(self, resource_group, location, storage_account, storage_account_key): diff --git a/sdk/storage/azure-storage-blob/tests/test_block_blob_sync_copy.py b/sdk/storage/azure-storage-blob/tests/test_block_blob_sync_copy.py index 56c44211ac70..9f95fcb5790f 100644 --- a/sdk/storage/azure-storage-blob/tests/test_block_blob_sync_copy.py +++ b/sdk/storage/azure-storage-blob/tests/test_block_blob_sync_copy.py @@ -1,3 +1,5 @@ +# coding: utf-8 + # ------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for @@ -40,12 +42,17 @@ def _setup(self, storage_account, key): # create source blob to be copied from self.source_blob_name = self.get_resource_name('srcblob') + self.source_blob_name_with_special_chars = 'भारत¥test/testsubÐirÍ/'+self.get_resource_name('srcÆblob') self.source_blob_data = self.get_random_bytes(SOURCE_BLOB_SIZE) + self.source_blob_with_special_chars_data = self.get_random_bytes(SOURCE_BLOB_SIZE) blob = self.bsc.get_blob_client(self.container_name, self.source_blob_name) + blob_with_special_chars = self.bsc.get_blob_client(self.container_name, self.source_blob_name_with_special_chars) + if self.is_live: self.bsc.create_container(self.container_name) blob.upload_blob(self.source_blob_data) + blob_with_special_chars.upload_blob(self.source_blob_with_special_chars_data) # generate a SAS so that it is accessible with a URL sas_token = generate_blob_sas( @@ -57,7 +64,19 @@ def _setup(self, storage_account, key): permission=BlobSasPermissions(read=True), expiry=datetime.utcnow() + timedelta(hours=1), ) + # generate a SAS so that it is accessible with a URL + sas_token_for_special_chars = generate_blob_sas( + blob_with_special_chars.account_name, + blob_with_special_chars.container_name, + blob_with_special_chars.blob_name, + snapshot=blob_with_special_chars.snapshot, + account_key=blob_with_special_chars.credential.account_key, + permission=BlobSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=1), + ) self.source_blob_url = BlobClient.from_blob_url(blob.url, credential=sas_token).url + self.source_blob_url_with_special_chars = BlobClient.from_blob_url( + blob_with_special_chars.url, credential=sas_token_for_special_chars).url @GlobalStorageAccountPreparer() def test_put_block_from_url_and_commit(self, resource_group, location, storage_account, storage_account_key): @@ -91,6 +110,30 @@ def test_put_block_from_url_and_commit(self, resource_group, location, storage_a self.assertEqual(len(content), 8 * 1024) self.assertEqual(content, self.source_blob_data) + dest_blob.stage_block_from_url( + block_id=3, + source_url=self.source_blob_url_with_special_chars, + source_offset=0, + source_length=split) + dest_blob.stage_block_from_url( + block_id=4, + source_url=self.source_blob_url_with_special_chars, + source_offset=split, + source_length=split) + + # Assert blocks + committed, uncommitted = dest_blob.get_block_list('all') + self.assertEqual(len(uncommitted), 2) + self.assertEqual(len(committed), 2) + + # Act part 2: commit the blocks + dest_blob.commit_block_list(['3', '4']) + + # Assert destination blob has right content + content = dest_blob.download_blob().readall() + self.assertEqual(len(content), 8 * 1024) + self.assertEqual(content, self.source_blob_with_special_chars_data) + @GlobalStorageAccountPreparer() def test_put_block_from_url_and_validate_content_md5(self, resource_group, location, storage_account, storage_account_key): self._setup(storage_account, storage_account_key) @@ -145,6 +188,17 @@ def test_copy_blob_sync(self, resource_group, location, storage_account, storage content = dest_blob.download_blob().readall() self.assertEqual(self.source_blob_data, content) + copy_props_with_special_chars = dest_blob.start_copy_from_url(self.source_blob_url_with_special_chars, requires_sync=True) + + # Assert + self.assertIsNotNone(copy_props_with_special_chars) + self.assertIsNotNone(copy_props_with_special_chars['copy_id']) + self.assertEqual('success', copy_props_with_special_chars['copy_status']) + + # Verify content + content = dest_blob.download_blob().readall() + self.assertEqual(self.source_blob_with_special_chars_data, content) + @pytest.mark.playback_test_only @GlobalStorageAccountPreparer() def test_sync_copy_blob_returns_vid(self, resource_group, location, storage_account, storage_account_key): diff --git a/sdk/storage/azure-storage-blob/tests/test_page_blob.py b/sdk/storage/azure-storage-blob/tests/test_page_blob.py index c08384d0b345..de618198ef0e 100644 --- a/sdk/storage/azure-storage-blob/tests/test_page_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_page_blob.py @@ -68,6 +68,13 @@ def _create_blob(self, bsc, length=512, sequence_number=None, tags=None): blob.create_page_blob(size=length, sequence_number=sequence_number, tags=tags) return blob + def _create_source_blob_with_special_chars(self, bs, data, offset, length): + blob_client = bs.get_blob_client(self.source_container_name, + 'भारत¥test/testsubÐirÍ/'+self.get_resource_name('srcÆblob')) + blob_client.create_page_blob(size=length) + blob_client.upload_page(data, offset=offset, length=length) + return blob_client + def _create_source_blob(self, bs, data, offset, length): blob_client = bs.get_blob_client(self.source_container_name, self.get_resource_name(TEST_BLOB_PREFIX)) @@ -407,6 +414,9 @@ def test_upload_pages_from_url(self, resource_group, location, storage_account, self._setup(bsc) source_blob_data = self.get_random_bytes(SOURCE_BLOB_SIZE) source_blob_client = self._create_source_blob(bsc, source_blob_data, 0, SOURCE_BLOB_SIZE) + source_blob_client_with_special_chars = self._create_source_blob_with_special_chars( + bsc, source_blob_data, 0, SOURCE_BLOB_SIZE) + sas = generate_blob_sas( source_blob_client.account_name, source_blob_client.container_name, @@ -416,6 +426,15 @@ def test_upload_pages_from_url(self, resource_group, location, storage_account, permission=BlobSasPermissions(read=True, delete=True), expiry=datetime.utcnow() + timedelta(hours=1)) + sas_token_for_blob_with_special_chars = generate_blob_sas( + source_blob_client_with_special_chars.account_name, + source_blob_client_with_special_chars.container_name, + source_blob_client_with_special_chars.blob_name, + snapshot=source_blob_client_with_special_chars.snapshot, + account_key=source_blob_client_with_special_chars.credential.account_key, + permission=BlobSasPermissions(read=True, delete=True), + expiry=datetime.utcnow() + timedelta(hours=1)) + destination_blob_client = self._create_blob(bsc, length=SOURCE_BLOB_SIZE) # Act: make update page from url calls @@ -437,6 +456,19 @@ def test_upload_pages_from_url(self, resource_group, location, storage_account, self.assertEqual(blob_properties.get('etag'), resp.get('etag')) self.assertEqual(blob_properties.get('last_modified'), resp.get('last_modified')) + # Act: make update page from url calls + source_with_special_chars_resp = destination_blob_client.upload_pages_from_url( + source_blob_client_with_special_chars.url + "?" + sas_token_for_blob_with_special_chars, offset=0, length=4 * 1024, source_offset=0) + self.assertIsNotNone(source_with_special_chars_resp.get('etag')) + self.assertIsNotNone(source_with_special_chars_resp.get('last_modified')) + + # Assert the destination blob is constructed correctly + blob_properties = destination_blob_client.get_blob_properties() + self.assertEqual(blob_properties.size, SOURCE_BLOB_SIZE) + self.assertBlobEqual(self.container_name, destination_blob_client.blob_name, source_blob_data, bsc) + self.assertEqual(blob_properties.get('etag'), source_with_special_chars_resp.get('etag')) + self.assertEqual(blob_properties.get('last_modified'), source_with_special_chars_resp.get('last_modified')) + @GlobalStorageAccountPreparer() def test_upload_pages_from_url_and_validate_content_md5(self, resource_group, location, storage_account, storage_account_key): # Arrange diff --git a/sdk/tables/azure-data-tables/MANIFEST.in b/sdk/tables/azure-data-tables/MANIFEST.in index 428787a39347..c6292d45f925 100644 --- a/sdk/tables/azure-data-tables/MANIFEST.in +++ b/sdk/tables/azure-data-tables/MANIFEST.in @@ -1,6 +1,7 @@ +recursive-include tests *.py *.yaml include *.md include azure/__init__.py include azure/data/__init__.py include LICENSE.txt recursive-include tests *.py -recursive-include samples *.py *.md +recursive-include samples *.py *.md \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/README.md b/sdk/tables/azure-data-tables/README.md index a29e4d753e2c..9d5aaa1546f5 100644 --- a/sdk/tables/azure-data-tables/README.md +++ b/sdk/tables/azure-data-tables/README.md @@ -1,16 +1,17 @@ # Azure Data Tables client library for Python -Azure Data Tables is a NoSQL data storing service that can be accessed from anywhere in the world via authenticated calls using HTTP or HTTPS. -Tables scale as needed to support the amount of data inserted, and allow for the storing of data with non-complex accessing. -Tables scale as needed to support the amount of data inserted, and allow for the storing of data with non-complex accessing. +Azure Data Tables is a NoSQL data storing service that can be accessed from anywhere in the world via authenticated calls using HTTP or HTTPS. +Tables scale as needed to support the amount of data inserted, and allow for the storing of data with non-complex accessing. The Azure Data Tables client can be used to access Azure Storage or Cosmos accounts. -Common uses of Azure Data Tables include: -* Storing structured data in the form of tables +# Usage +* Storing structured data in the form of tables # Usage * Quickly querying data using a clustered index [Source code](https://github.com/Azure/azure-sdk-for-python/tree/master/sdk) | [Package (PyPI)](https://pypi.org) | [API reference documentation](https://aka.ms/azsdk/python/tables/docs) | [Product documentation](https://docs.microsoft.com/azure/storage/) | [Samples](https://github.com/Azure/azure-sdk-for-python/tree/master/sdk) +For code examples, see [MyService Management](https://docs.microsoft.com/python/api/overview/azure/) +on docs.microsoft.com. ## Getting started @@ -38,7 +39,6 @@ or [Azure CLI](https://docs.microsoft.com/azure/storage/common/storage-quickstar # Create a new resource group to hold the storage account - # if using an existing resource group, skip this step az group create --name MyResourceGroup --location westus2 - # Create the storage account az storage account create -n mystorageaccount -g MyResourceGroup ``` @@ -51,12 +51,11 @@ you to access the account: ```python from azure.data.tables import TableServiceClient - service = TableServiceClient(account_url="https://.table.core.windows.net/", credential=credential) ``` #### Looking up the account URL -You can find the account's table service URL using the +You can find the account's table service URL using the [Azure Portal](https://docs.microsoft.com/azure/storage/common/storage-account-overview#storage-account-endpoints), [Azure PowerShell](https://docs.microsoft.com/powershell/module/az.storage/get-azstorageaccount), or [Azure CLI](https://docs.microsoft.com/cli/azure/storage/account?view=azure-cli-latest#az-storage-account-show): @@ -77,7 +76,7 @@ The `credential` parameter may be provided in a number of different forms, depen ```python from datetime import datetime, timedelta from azure.data.tables import TableServiceClient, generate_account_sas, ResourceTypes, AccountSasPermissions - + sas_token = generate_account_sas( account_name="", account_key="", @@ -85,16 +84,15 @@ The `credential` parameter may be provided in a number of different forms, depen permission=AccountSasPermissions(read=True), expiry=datetime.utcnow() + timedelta(hours=1) ) - + table_service_client = TableServiceClient(account_url="https://.table.core.windows.net", credential=sas_token) ``` 2. To use an account [shared key](https://docs.microsoft.com/rest/api/storageservices/authenticate-with-shared-key/) - (aka account key or access key), provide the key as a string. This can be found in the Azure Portal under the "Access Keys" + (aka account key or access key), provide the key as a string. This can be found in the Azure Portal under the "Access Keys" section or by running the following Azure CLI command: ```az storage account keys list -g MyResourceGroup -n mystorageaccount``` - Use the key as the credential parameter to authenticate the client: ```python from azure.data.tables import TableServiceClient @@ -103,12 +101,11 @@ The `credential` parameter may be provided in a number of different forms, depen #### Creating the client from a connection string Depending on your use case and authorization method, you may prefer to initialize a client instance with a -connection string instead of providing the account URL and credential separately. To do this, pass the +connection string instead of providing the account URL and credential separately. To do this, pass the connection string to the client's `from_connection_string` class method: ```python from azure.data.tables import TableServiceClient - connection_string = "DefaultEndpointsProtocol=https;AccountName=xxxx;AccountKey=xxxx;EndpointSuffix=core.windows.net" service = TableServiceClient.from_connection_string(conn_str=connection_string) ``` @@ -139,14 +136,14 @@ Two different clients are provided to to interact with the various components of this client represents interaction with a specific table (which need not exist yet). It provides operations to create, delete, or update a table and includes operations to query, get, and upsert entities within it. - + ### Entities * **Create** - Adds an entity to the table. * **Delete** - Deletes an entity from the table. * **Update** - Updates an entities information by either merging or replacing the existing entity. * **Query** - Queries existing entities in a table based off of the QueryOptions (OData). * **Get** - Gets a specific entity from a table by partition and row key. -* **Upsert** - Merges or replaces an entity in a table, or if the entity does not exist, inserts the entity. +* **Upsert** - Merges or replaces an entity in a table, or if the entity does not exist, inserts the entity. ## Examples @@ -162,7 +159,6 @@ Create a table in your account ```python from azure.data.tables import TableServiceClient - table_service_client = TableServiceClient.from_connection_string(conn_str="") table_service_client.create_table(table_name="myTable") ``` @@ -172,9 +168,7 @@ Create entities in the table ```python from azure.data.tables import TableClient - my_entity = {'PartitionKey':'part','RowKey':'row'} - table_client = TableClient.from_connection_string(conn_str="", table_name="myTable") entity = table_client.create_entity(entity=my_entity) ``` @@ -184,9 +178,7 @@ Querying entities in the table ```python from azure.data.tables import TableClient - my_filter = "text eq Marker" - table_client = TableClient.from_connection_string(conn_str="", table_name="mytable") entity = table_client.query_entities(filter=my_filter) ``` @@ -246,15 +238,12 @@ headers, can be enabled on a client with the `logging_enable` argument: import sys import logging from azure.data.tables import TableServiceClient - # Create a logger for the 'azure.data.tables' SDK logger = logging.getLogger('azure.data.tables') logger.setLevel(logging.DEBUG) - # Configure a console output handler = logging.StreamHandler(stream=sys.stdout) logger.addHandler(handler) - # This client will log detailed information about its HTTP sessions, at DEBUG level service_client = TableServiceClient.from_connection_string("your_connection_string", logging_enable=True) ``` @@ -287,7 +276,7 @@ Several Azure Data Tables Python SDK samples are available to you in the SDK's G * Query entities * Update entities * Upsert entities - + ### Additional documentation For more extensive documentation on Azure Data Tables, see the [Azure Data Tables documentation](https://docs.microsoft.com/azure/storage/tables/) on docs.microsoft.com. @@ -296,4 +285,4 @@ This project welcomes contributions and suggestions. Most contributions require When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py b/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py index 36b2cb3fd59c..49eb7f5bf3f7 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py @@ -50,7 +50,8 @@ StorageRequestHook, StorageResponseHook, StorageLoggingPolicy, - StorageHosts, ExponentialRetry, + StorageHosts, + TablesRetryPolicy, ) from ._error import _process_table_error from ._models import PartialBatchErrorException @@ -391,7 +392,7 @@ def create_configuration(**kwargs): config.headers_policy = StorageHeadersPolicy(**kwargs) config.user_agent_policy = UserAgentPolicy(sdk_moniker=SDK_MONIKER, **kwargs) # sdk_moniker="storage-{}/{}".format(kwargs.pop('storage_sdk'), VERSION), **kwargs) - config.retry_policy = kwargs.get("retry_policy") or ExponentialRetry(**kwargs) + config.retry_policy = kwargs.get("retry_policy") or TablesRetryPolicy(**kwargs) config.logging_policy = StorageLoggingPolicy(**kwargs) config.proxy_policy = ProxyPolicy(**kwargs) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py b/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py index 6b16a8f288bd..913e5de9a0a5 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py @@ -160,7 +160,10 @@ def _convert_to_entity(entry_element): # Add type for Int32 if type(value) is int: # pylint:disable=C0123 - mtype = EdmType.INT32 + if value.bit_length() <= 32: + mtype = EdmType.INT32 + else: + mtype = EdmType.INT64 # no type info, property should parse automatically if not mtype: diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_entity.py b/sdk/tables/azure-data-tables/azure/data/tables/_entity.py index 230eb402e16d..41e9a3a1d588 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_entity.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_entity.py @@ -101,7 +101,10 @@ def __init__(self, elif isinstance(value, bool): self.type = EdmType.BOOLEAN elif isinstance(value, six.integer_types): - self.type = EdmType.INT64 + if value.bit_length() <= 32: + self.type = EdmType.INT32 + else: + self.type = EdmType.INT64 elif isinstance(value, datetime): self.type = EdmType.DATETIME elif isinstance(value, float): diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_policies.py b/sdk/tables/azure-data-tables/azure/data/tables/_policies.py index 732779f3943b..9114217bf286 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_policies.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_policies.py @@ -36,7 +36,8 @@ SansIOHTTPPolicy, NetworkTraceLoggingPolicy, HTTPPolicy, - RequestHistory + RequestHistory, + RetryPolicy ) from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError @@ -353,18 +354,61 @@ def on_response(self, request, response): ) -class StorageRetryPolicy(HTTPPolicy): +class TablesRetryPolicy(RetryPolicy): """ - The base class for Exponential and Linear retries containing shared code. + A base class for retry policies for the Table Client and Table Service Client """ + def __init__( + self, + initial_backoff=15, # type: int + increment_base=3, # type: int + retry_total=10, # type: int + retry_to_secondary=False, # type: bool + random_jitter_range=3, # type: int + **kwargs # type: Any + ): + """ + Build a TablesRetryPolicy object. - def __init__(self, **kwargs): - self.total_retries = kwargs.pop('retry_total', 10) + :param int initial_backoff: + The initial backoff interval, in seconds, for the first retry. + :param int increment_base: + The base, in seconds, to increment the initial_backoff by after the + first retry. + :param int retry_total: total number of retries + :param bool retry_to_secondary: + Whether the request should be retried to secondary, if able. This should + only be enabled of RA-GRS accounts are used and potentially stale data + can be handled. + :param int random_jitter_range: + A number in seconds which indicates a range to jitter/randomize for the back-off interval. + For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3. + """ + self.initial_backoff = initial_backoff + self.increment_base = increment_base + self.random_jitter_range = random_jitter_range + self.total_retries = retry_total self.connect_retries = kwargs.pop('retry_connect', 3) self.read_retries = kwargs.pop('retry_read', 3) self.status_retries = kwargs.pop('retry_status', 3) - self.retry_to_secondary = kwargs.pop('retry_to_secondary', False) - super(StorageRetryPolicy, self).__init__() + self.retry_to_secondary = retry_to_secondary + super(TablesRetryPolicy, self).__init__(**kwargs) + + def get_backoff_time(self, settings): + """ + Calculates how long to sleep before retrying. + :param dict settings: + :keyword callable cls: A custom type or function that will be passed the direct response + :return: + An integer indicating how long to wait before retrying the request, + or None to indicate no retry should be performed. + :rtype: int or None + """ + random_generator = random.Random() + backoff = self.initial_backoff + (0 if settings['count'] == 0 else pow(self.increment_base, settings['count'])) + random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0 + random_range_end = backoff + self.random_jitter_range + return random_generator.uniform(random_range_start, random_range_end) def _set_next_host_location(self, settings, request): # pylint: disable=no-self-use """ @@ -384,7 +428,7 @@ def _set_next_host_location(self, settings, request): # pylint: disable=no-self updated = url._replace(netloc=settings['hosts'].get(settings['mode'])) request.url = updated.geturl() - def configure_retries(self, request): # pylint: disable=no-self-use + def configure_retries(self, request): # pylint: disable=no-self-use, arguments-differ # type: (...)-> dict """ :param Any request: @@ -414,17 +458,8 @@ def configure_retries(self, request): # pylint: disable=no-self-use 'history': [] } - def get_backoff_time(self, settings, **kwargs): # pylint: disable=unused-argument,no-self-use - """ Formula for computing the current backoff. - Should be calculated by child class. - :param Any settings: - :keyword callable cls: A custom type or function that will be passed the direct response - :rtype: float - """ - return 0 - - def sleep(self, settings, transport): - # type: (...)->None + def sleep(self, settings, transport): # pylint: disable=arguments-differ + # type: (...) -> None """ :param Any settings: :param Any transport: @@ -435,7 +470,7 @@ def sleep(self, settings, transport): return transport.sleep(backoff) - def increment(self, settings, request, response=None, error=None, **kwargs): # pylint:disable=W0613 + def increment(self, settings, request, response=None, error=None, **kwargs): # pylint:disable=unused-argument, arguments-differ # type: (...)->None """Increment the retry counters. @@ -531,7 +566,7 @@ def send(self, request): return response -class ExponentialRetry(StorageRetryPolicy): +class ExponentialRetry(TablesRetryPolicy): """Exponential retry.""" def __init__(self, initial_backoff=15, increment_base=3, retry_total=3, @@ -565,10 +600,9 @@ def __init__(self, initial_backoff=15, increment_base=3, retry_total=3, super(ExponentialRetry, self).__init__( retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) - def get_backoff_time(self, settings, **kwargs): + def get_backoff_time(self, settings): """ Calculates how long to sleep before retrying. - :param **kwargs: :param dict settings: :keyword callable cls: A custom type or function that will be passed the direct response :return: @@ -583,7 +617,7 @@ def get_backoff_time(self, settings, **kwargs): return random_generator.uniform(random_range_start, random_range_end) -class LinearRetry(StorageRetryPolicy): +class LinearRetry(TablesRetryPolicy): """Linear retry.""" def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_jitter_range=3, **kwargs): @@ -608,7 +642,7 @@ def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_j super(LinearRetry, self).__init__( retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) - def get_backoff_time(self, settings, **kwargs): + def get_backoff_time(self, settings): """ Calculates how long to sleep before retrying. diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_serialize.py b/sdk/tables/azure-data-tables/azure/data/tables/_serialize.py index f937d68f3b72..56e7cae7598d 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_serialize.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_serialize.py @@ -133,6 +133,15 @@ def _to_entity_int64(value): return EdmType.INT64, str(value) +def _to_entity_int(value): + ivalue = int(value) + if ivalue.bit_length() <= 32: + return _to_entity_int32(value) + if ivalue.bit_length() <= 64: + return _to_entity_int64(value) + raise TypeError(_ERROR_VALUE_TOO_LARGE.format(str(value), EdmType.INT64)) + + def _to_entity_str(value): return None, value @@ -144,7 +153,7 @@ def _to_entity_none(value): # pylint:disable=W0613 # Conversion from Python type to a function which returns a tuple of the # type string and content string. _PYTHON_TO_ENTITY_CONVERSIONS = { - int: _to_entity_int64, + int: _to_entity_int, bool: _to_entity_bool, datetime: _to_entity_datetime, float: _to_entity_float, diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py b/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py index e55dbd0ddc4f..47861d843a87 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py @@ -20,14 +20,18 @@ from ._deserialize import _convert_to_entity, _trim_service_metadata from ._entity import TableEntity from ._generated import AzureTable -from ._generated.models import AccessPolicy, SignedIdentifier, TableProperties, QueryOptions +from ._generated.models import ( + AccessPolicy, + SignedIdentifier, + TableProperties, + QueryOptions +) from ._serialize import _get_match_headers, _add_entity_properties from ._base_client import parse_connection_str from ._table_client_base import TableClientBase from ._serialize import serialize_iso from ._deserialize import _return_headers_and_deserialized from ._error import _process_table_error -from ._version import VERSION from ._models import TableEntityPropertiesPaged, UpdateMode @@ -59,7 +63,6 @@ def __init__( """ super(TableClient, self).__init__(account_url, table_name, credential=credential, **kwargs) self._client = AzureTable(self.url, pipeline=self._pipeline) - self._client._config.version = kwargs.get('api_version', VERSION) # pylint: disable=protected-access @classmethod def from_connection_string( diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py b/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py index 28186410fe16..18634fbb618e 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py @@ -6,7 +6,7 @@ import functools from typing import Any, Union -from azure.core.exceptions import HttpResponseError +from azure.core.exceptions import HttpResponseError, ResourceExistsError from azure.core.paging import ItemPaged from azure.core.tracing.decorator import distributed_trace from azure.core.pipeline import Pipeline @@ -18,7 +18,6 @@ from ._base_client import parse_connection_str, TransportWrapper from ._models import LocationMode from ._error import _process_table_error -from ._version import VERSION from ._table_client import TableClient from ._table_service_client_base import TableServiceClientBase @@ -47,7 +46,6 @@ def __init__( super(TableServiceClient, self).__init__(account_url, service='table', credential=credential, **kwargs) self._client = AzureTable(self.url, pipeline=self._pipeline) - self._client._config.version = kwargs.get('api_version', VERSION) # pylint: disable=protected-access @classmethod def from_connection_string( @@ -156,6 +154,30 @@ def create_table( table.create_table(**kwargs) return table + @distributed_trace + def create_table_if_not_exists( + self, + table_name, # type: str + **kwargs # type: Any + ): + # type: (...) -> TableClient + """Creates a new table if it does not currently exist. + If the table currently exists, the current table is + returned. + + :param table_name: The Table name. + :type table_name: str + :return: TableClient + :rtype: ~azure.data.tables.TableClient + :raises: ~azure.core.exceptions.HttpResponseError + """ + table = self.get_table_client(table_name=table_name) + try: + table.create_table(**kwargs) + except ResourceExistsError: + pass + return table + @distributed_trace def delete_table( self, diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py index 88695af888aa..fe855465a0b8 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py @@ -9,10 +9,10 @@ import logging from typing import Any, TYPE_CHECKING -from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.policies import AsyncHTTPPolicy, AsyncRetryPolicy from azure.core.exceptions import AzureError -from .._policies import is_retry, StorageRetryPolicy +from .._policies import is_retry, TablesRetryPolicy if TYPE_CHECKING: from azure.core.pipeline import PipelineRequest, PipelineResponse @@ -78,12 +78,56 @@ async def send(self, request): request.context['response_callback'] = response_callback return response -class AsyncStorageRetryPolicy(StorageRetryPolicy): - """ - The base class for Exponential and Linear retries containing shared code. - """ - async def sleep(self, settings, transport): # pylint: disable =W0236 +class AsyncTablesRetryPolicy(AsyncRetryPolicy, TablesRetryPolicy): + """Exponential retry.""" + + def __init__(self, initial_backoff=15, increment_base=3, retry_total=3, + retry_to_secondary=False, random_jitter_range=3, **kwargs): + ''' + Constructs an Exponential retry object. The initial_backoff is used for + the first retry. Subsequent retries are retried after initial_backoff + + increment_power^retry_count seconds. For example, by default the first retry + occurs after 15 seconds, the second after (15+3^1) = 18 seconds, and the + third after (15+3^2) = 24 seconds. + + :param int initial_backoff: + The initial backoff interval, in seconds, for the first retry. + :param int increment_base: + The base, in seconds, to increment the initial_backoff by after the + first retry. + :param int max_attempts: + The maximum number of retry attempts. + :param bool retry_to_secondary: + Whether the request should be retried to secondary, if able. This should + only be enabled of RA-GRS accounts are used and potentially stale data + can be handled. + :param int random_jitter_range: + A number in seconds which indicates a range to jitter/randomize for the back-off interval. + For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3. + ''' + self.initial_backoff = initial_backoff + self.increment_base = increment_base + self.random_jitter_range = random_jitter_range + super(AsyncTablesRetryPolicy, self).__init__( + retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) + + def get_backoff_time(self, settings): + """ + Calculates how long to sleep before retrying. + + :return: + An integer indicating how long to wait before retrying the request, + or None to indicate no retry should be performed. + :rtype: int or None + """ + random_generator = random.Random() + backoff = self.initial_backoff + (0 if settings['count'] == 0 else pow(self.increment_base, settings['count'])) + random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0 + random_range_end = backoff + self.random_jitter_range + return random_generator.uniform(random_range_start, random_range_end) + + async def sleep(self, settings, transport): # pylint: disable=W0236, arguments-differ backoff = self.get_backoff_time(settings) if not backoff or backoff < 0: return @@ -99,7 +143,6 @@ async def send(self, request): # pylint: disable =W0236 if is_retry(response, retry_settings['mode']): retries_remaining = self.increment( retry_settings, - request=request.http_request, response=response.http_response) if retries_remaining: await retry_hook( @@ -111,8 +154,7 @@ async def send(self, request): # pylint: disable =W0236 continue break except AzureError as err: - retries_remaining = self.increment( - retry_settings, request=request.http_request, error=err) + retries_remaining = self.increment(retry_settings, error=err) if retries_remaining: await retry_hook( retry_settings, @@ -128,7 +170,7 @@ async def send(self, request): # pylint: disable =W0236 return response -class ExponentialRetry(AsyncStorageRetryPolicy): +class ExponentialRetry(AsyncTablesRetryPolicy): """Exponential retry.""" def __init__(self, initial_backoff=15, increment_base=3, retry_total=3, @@ -161,11 +203,10 @@ def __init__(self, initial_backoff=15, increment_base=3, retry_total=3, super(ExponentialRetry, self).__init__( retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) - def get_backoff_time(self, settings, **kwargs): + def get_backoff_time(self, settings): """ Calculates how long to sleep before retrying. - :param **kwargs: :return: An integer indicating how long to wait before retrying the request, or None to indicate no retry should be performed. @@ -178,7 +219,7 @@ def get_backoff_time(self, settings, **kwargs): return random_generator.uniform(random_range_start, random_range_end) -class LinearRetry(AsyncStorageRetryPolicy): +class LinearRetry(AsyncTablesRetryPolicy): """Linear retry.""" def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_jitter_range=3, **kwargs): @@ -202,7 +243,7 @@ def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_j super(LinearRetry, self).__init__( retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) - def get_backoff_time(self, settings, **kwargs): + def get_backoff_time(self, settings, **kwargs): # pylint: disable=unused-argument """ Calculates how long to sleep before retrying. diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py index c8fd815d489b..74e7d637da21 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py @@ -20,7 +20,6 @@ from azure.core.tracing.decorator import distributed_trace from azure.core.tracing.decorator_async import distributed_trace_async -from .. import VERSION from .._base_client import parse_connection_str from .._entity import TableEntity from .._generated.aio import AzureTable @@ -71,7 +70,6 @@ def __init__( account_url, table_name=table_name, credential=credential, loop=loop, **kwargs ) self._client = AzureTable(self.url, pipeline=self._pipeline, loop=loop) - self._client._config.version = kwargs.get('api_version', VERSION) # pylint: disable = W0212 self._loop = loop @classmethod diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py index 50c8c1570f0c..0056c33c19d8 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py @@ -11,12 +11,12 @@ ) from azure.core.async_paging import AsyncItemPaged -from azure.core.exceptions import HttpResponseError +from azure.core.exceptions import HttpResponseError, ResourceExistsError from azure.core.pipeline import AsyncPipeline from azure.core.tracing.decorator import distributed_trace from azure.core.tracing.decorator_async import distributed_trace_async -from .. import VERSION, LocationMode +from .. import LocationMode from .._base_client import parse_connection_str from .._generated.aio._azure_table_async import AzureTable from .._generated.models import TableServiceProperties, TableProperties, QueryOptions @@ -84,7 +84,6 @@ def __init__( loop=loop, **kwargs) self._client = AzureTable(url=self.url, pipeline=self._pipeline, loop=loop) # type: ignore - self._client._config.version = kwargs.get('api_version', VERSION) # pylint: disable=protected-access self._loop = loop @classmethod @@ -197,6 +196,30 @@ async def create_table( await table.create_table(**kwargs) return table + @distributed_trace_async + async def create_table_if_not_exists( + self, + table_name, # type: str + **kwargs # type: Any + ): + # type: (...) -> TableClient + """Creates a new table if it does not currently exist. + If the table currently exists, the current table is + returned. + + :param table_name: The Table name. + :type table_name: str + :return: TableClient + :rtype: ~azure.data.tables.aio.TableClient + :raises: ~azure.core.exceptions.HttpResponseError + """ + table = self.get_table_client(table_name=table_name) + try: + await table.create_table(**kwargs) + except ResourceExistsError: + pass + return table + @distributed_trace_async async def delete_table( self, diff --git a/sdk/tables/azure-data-tables/dev_requirements.txt b/sdk/tables/azure-data-tables/dev_requirements.txt index 5547db3c87f7..4a940812c683 100644 --- a/sdk/tables/azure-data-tables/dev_requirements.txt +++ b/sdk/tables/azure-data-tables/dev_requirements.txt @@ -3,4 +3,5 @@ ../../core/azure-core cryptography>=2.1.4 aiohttp>=3.0; python_version >= '3.5' - +azure-identity +../azure-data-nspkg \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/sdk_packaging.toml b/sdk/tables/azure-data-tables/sdk_packaging.toml new file mode 100644 index 000000000000..e7687fdae93b --- /dev/null +++ b/sdk/tables/azure-data-tables/sdk_packaging.toml @@ -0,0 +1,2 @@ +[packaging] +auto_update = false \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/setup.py b/sdk/tables/azure-data-tables/setup.py index 9cf68df37c3d..1651b14ff4cc 100644 --- a/sdk/tables/azure-data-tables/setup.py +++ b/sdk/tables/azure-data-tables/setup.py @@ -70,6 +70,7 @@ # Exclude packages that will be covered by PEP420 or nspkg 'azure', 'tests', + 'azure.data', ]), install_requires=[ "azure-core<2.0.0,>=1.2.2", @@ -77,8 +78,8 @@ # azure-data-tables ], extras_require={ - ":python_version<'3.0'": ['futures'], + ":python_version<'3.0'": ['futures', 'azure-data-nspkg<2.0.0,>=1.0.0'], ":python_version<'3.4'": ['enum34>=1.0.4'], ":python_version<'3.5'": ["typing"] }, -) +) \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/tests/_shared/testcase.py b/sdk/tables/azure-data-tables/tests/_shared/testcase.py index 3dcfde01bf48..d20eb80556db 100644 --- a/sdk/tables/azure-data-tables/tests/_shared/testcase.py +++ b/sdk/tables/azure-data-tables/tests/_shared/testcase.py @@ -13,9 +13,6 @@ import time from datetime import datetime, timedelta -from azure.data.tables import ResourceTypes, AccountSasPermissions -from azure.data.tables._table_shared_access_signature import generate_account_sas - try: import unittest.mock as mock except ImportError: @@ -42,8 +39,8 @@ from io import StringIO from azure.core.credentials import AccessToken -#from azure.data.tabless import generate_account_sas, AccountSasPermissions, ResourceTypes from azure.mgmt.storage.models import StorageAccount, Endpoints +from azure.data.tables import generate_account_sas, AccountSasPermissions, ResourceTypes try: from devtools_testutils import mgmt_settings_real as settings @@ -351,6 +348,9 @@ def storage_account(): i_need_to_create_rg = not (existing_rg_name or existing_storage_name or storage_connection_string) got_storage_info_from_env = existing_storage_name or storage_connection_string + storage_name = None + rg_kwargs = {} + try: if i_need_to_create_rg: rg_name, rg_kwargs = rg_preparer._prepare_create_resource(test_case) @@ -431,11 +431,12 @@ def build_service_endpoint(service): TableTestCase._STORAGE_CONNECTION_STRING = storage_connection_string yield finally: - if not got_storage_info_from_env: - storage_preparer.remove_resource( - storage_name, - resource_group=rg - ) + if storage_name is not None: + if not got_storage_info_from_env: + storage_preparer.remove_resource( + storage_name, + resource_group=rg + ) finally: if i_need_to_create_rg: rg_preparer.remove_resource(rg_name) diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_fail_on_exist.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_fail_on_exist.yaml index e4d78057d153..9915930aa06f 100644 --- a/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_fail_on_exist.yaml +++ b/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_fail_on_exist.yaml @@ -15,11 +15,11 @@ interactions: DataServiceVersion: - '3.0' Date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:19 GMT User-Agent: - - azsdk-python-storage-table/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) x-ms-date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:19 GMT x-ms-version: - '2019-07-07' method: POST @@ -33,7 +33,7 @@ interactions: content-type: - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT location: - https://storagename.table.core.windows.net/Tables('pytablesync6d7c1113') server: @@ -63,11 +63,11 @@ interactions: DataServiceVersion: - '3.0' Date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT User-Agent: - - azsdk-python-storage-table/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) x-ms-date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT x-ms-version: - '2019-07-07' method: POST @@ -75,14 +75,14 @@ interactions: response: body: string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The - table specified already exists.\nRequestId:fbae76e2-b002-009f-206e-7649d9000000\nTime:2020-08-19T21:18:00.8893697Z"}}}' + table specified already exists.\nRequestId:2e7b208e-d002-003b-7bb8-7c685f000000\nTime:2020-08-27T21:28:21.0100931Z"}}}' headers: cache-control: - no-cache content-type: - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT server: - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 transfer-encoding: @@ -106,11 +106,11 @@ interactions: Content-Length: - '0' Date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT User-Agent: - - azsdk-python-storage-table/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) x-ms-date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT x-ms-version: - '2019-07-07' method: DELETE @@ -124,7 +124,7 @@ interactions: content-length: - '0' date: - - Wed, 19 Aug 2020 21:18:00 GMT + - Thu, 27 Aug 2020 21:28:20 GMT server: - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 x-content-type-options: diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_if_exists.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_if_exists.yaml new file mode 100644 index 000000000000..106a61bdda08 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_if_exists.yaml @@ -0,0 +1,137 @@ +interactions: +- request: + body: '{"TableName": "pytablesync2c5a0f7d"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '36' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Thu, 27 Aug 2020 21:42:12 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:42:12 GMT + x-ms-version: + - '2019-07-07' + method: POST + uri: https://storagename.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://storagename.table.core.windows.net/$metadata#Tables/@Element","TableName":"pytablesync2c5a0f7d"}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Thu, 27 Aug 2020 21:42:12 GMT + location: + - https://storagename.table.core.windows.net/Tables('pytablesync2c5a0f7d') + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-07-07' + status: + code: 201 + message: Created +- request: + body: '{"TableName": "pytablesync2c5a0f7d"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '36' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Thu, 27 Aug 2020 21:42:12 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:42:12 GMT + x-ms-version: + - '2019-07-07' + method: POST + uri: https://storagename.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:64cfdefd-d002-0057-80ba-7c2831000000\nTime:2020-08-27T21:42:13.5449901Z"}}}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Thu, 27 Aug 2020 21:42:13 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-07-07' + status: + code: 409 + message: Conflict +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Thu, 27 Aug 2020 21:42:12 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:42:12 GMT + x-ms-version: + - '2019-07-07' + method: DELETE + uri: https://storagename.table.core.windows.net/Tables('pytablesync2c5a0f7d') + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Thu, 27 Aug 2020 21:42:13 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: + - nosniff + x-ms-version: + - '2019-07-07' + status: + code: 204 + message: No Content +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_if_exists_new_table.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_if_exists_new_table.yaml new file mode 100644 index 000000000000..e906065ec1dc --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_table.test_create_table_if_exists_new_table.yaml @@ -0,0 +1,90 @@ +interactions: +- request: + body: '{"TableName": "pytablesyncdd9e138d"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '36' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Thu, 27 Aug 2020 21:42:13 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:42:13 GMT + x-ms-version: + - '2019-07-07' + method: POST + uri: https://storagename.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://storagename.table.core.windows.net/$metadata#Tables/@Element","TableName":"pytablesyncdd9e138d"}' + headers: + cache-control: + - no-cache + content-type: + - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: + - Thu, 27 Aug 2020 21:42:13 GMT + location: + - https://storagename.table.core.windows.net/Tables('pytablesyncdd9e138d') + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ms-version: + - '2019-07-07' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Date: + - Thu, 27 Aug 2020 21:42:13 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:42:13 GMT + x-ms-version: + - '2019-07-07' + method: DELETE + uri: https://storagename.table.core.windows.net/Tables('pytablesyncdd9e138d') + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Thu, 27 Aug 2020 21:42:13 GMT + server: + - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: + - nosniff + x-ms-version: + - '2019-07-07' + status: + code: 204 + message: No Content +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_table_async.test_create_table_if_exists.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_table_async.test_create_table_if_exists.yaml new file mode 100644 index 000000000000..b71393defba6 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_table_async.test_create_table_if_exists.yaml @@ -0,0 +1,103 @@ +interactions: +- request: + body: '{"TableName": "pytableasync938b11fa"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '37' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Thu, 27 Aug 2020 21:49:52 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:49:52 GMT + x-ms-version: + - '2019-07-07' + method: POST + uri: https://storagename.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://storagename.table.core.windows.net/$metadata#Tables/@Element","TableName":"pytableasync938b11fa"}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Thu, 27 Aug 2020 21:49:53 GMT + location: https://storagename.table.core.windows.net/Tables('pytableasync938b11fa') + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-07-07' + status: + code: 201 + message: Created + url: https://pyacrstorageqqhkgfuuwjpy.table.core.windows.net/Tables +- request: + body: '{"TableName": "pytableasync938b11fa"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '37' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Thu, 27 Aug 2020 21:49:53 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:49:53 GMT + x-ms-version: + - '2019-07-07' + method: POST + uri: https://storagename.table.core.windows.net/Tables + response: + body: + string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The + table specified already exists.\nRequestId:6a7f283a-f002-0003-2dbb-7ccc9f000000\nTime:2020-08-27T21:49:54.0261635Z"}}}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Thu, 27 Aug 2020 21:49:53 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-07-07' + status: + code: 409 + message: Conflict + url: https://pyacrstorageqqhkgfuuwjpy.table.core.windows.net/Tables +- request: + body: null + headers: + Date: + - Thu, 27 Aug 2020 21:49:53 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:49:53 GMT + x-ms-version: + - '2019-07-07' + method: DELETE + uri: https://storagename.table.core.windows.net/Tables('pytableasync938b11fa') + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + date: Thu, 27 Aug 2020 21:49:53 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: nosniff + x-ms-version: '2019-07-07' + status: + code: 204 + message: No Content + url: https://pyacrstorageqqhkgfuuwjpy.table.core.windows.net/Tables('pytableasync938b11fa') +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_table_async.test_create_table_if_exists_new_table.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_table_async.test_create_table_if_exists_new_table.yaml new file mode 100644 index 000000000000..b8782ff27894 --- /dev/null +++ b/sdk/tables/azure-data-tables/tests/recordings/test_table_async.test_create_table_if_exists_new_table.yaml @@ -0,0 +1,66 @@ +interactions: +- request: + body: '{"TableName": "pytableasync5dc0160a"}' + headers: + Accept: + - application/json;odata=minimalmetadata + Content-Length: + - '37' + Content-Type: + - application/json;odata=nometadata + DataServiceVersion: + - '3.0' + Date: + - Thu, 27 Aug 2020 21:49:53 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:49:53 GMT + x-ms-version: + - '2019-07-07' + method: POST + uri: https://storagename.table.core.windows.net/Tables + response: + body: + string: '{"odata.metadata":"https://storagename.table.core.windows.net/$metadata#Tables/@Element","TableName":"pytableasync5dc0160a"}' + headers: + cache-control: no-cache + content-type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8 + date: Thu, 27 Aug 2020 21:49:54 GMT + location: https://storagename.table.core.windows.net/Tables('pytableasync5dc0160a') + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + transfer-encoding: chunked + x-content-type-options: nosniff + x-ms-version: '2019-07-07' + status: + code: 201 + message: Created + url: https://pyacrstorageqqhkgfuuwjpy.table.core.windows.net/Tables +- request: + body: null + headers: + Date: + - Thu, 27 Aug 2020 21:49:53 GMT + User-Agent: + - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + x-ms-date: + - Thu, 27 Aug 2020 21:49:53 GMT + x-ms-version: + - '2019-07-07' + method: DELETE + uri: https://storagename.table.core.windows.net/Tables('pytableasync5dc0160a') + response: + body: + string: '' + headers: + cache-control: no-cache + content-length: '0' + date: Thu, 27 Aug 2020 21:49:54 GMT + server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 + x-content-type-options: nosniff + x-ms-version: '2019-07-07' + status: + code: 204 + message: No Content + url: https://pyacrstorageqqhkgfuuwjpy.table.core.windows.net/Tables('pytableasync5dc0160a') +version: 1 diff --git a/sdk/tables/azure-data-tables/tests/recordings/test_table_entity.test_insert_entity_with_large_int32_value_throws.yaml b/sdk/tables/azure-data-tables/tests/recordings/test_table_entity.test_insert_entity_with_large_int32_value_throws.yaml index 65617ef23a9f..c944d845b6a3 100644 --- a/sdk/tables/azure-data-tables/tests/recordings/test_table_entity.test_insert_entity_with_large_int32_value_throws.yaml +++ b/sdk/tables/azure-data-tables/tests/recordings/test_table_entity.test_insert_entity_with_large_int32_value_throws.yaml @@ -15,13 +15,13 @@ interactions: DataServiceVersion: - '3.0' Date: - - Thu, 20 Aug 2020 20:16:40 GMT + - Mon, 31 Aug 2020 22:27:33 GMT User-Agent: - - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + - azsdk-python-data-tables/12.0.0b1 Python/3.8.4 (Windows-10-10.0.19041-SP0) x-ms-date: - - Thu, 20 Aug 2020 20:16:40 GMT + - Mon, 31 Aug 2020 22:27:33 GMT x-ms-version: - - '2019-07-07' + - '2019-02-02' method: POST uri: https://storagename.table.core.windows.net/Tables response: @@ -33,7 +33,7 @@ interactions: content-type: - application/json;odata=minimalmetadata;streaming=true;charset=utf-8 date: - - Thu, 20 Aug 2020 20:16:40 GMT + - Mon, 31 Aug 2020 22:27:30 GMT location: - https://storagename.table.core.windows.net/Tables('uttable8fac1b18') server: @@ -43,7 +43,7 @@ interactions: x-content-type-options: - nosniff x-ms-version: - - '2019-07-07' + - '2019-02-02' status: code: 201 message: Created @@ -59,13 +59,13 @@ interactions: Content-Length: - '0' Date: - - Thu, 20 Aug 2020 20:16:40 GMT + - Mon, 31 Aug 2020 22:27:33 GMT User-Agent: - - azsdk-python-data-tables/2019-07-07 Python/3.8.4 (Windows-10-10.0.19041-SP0) + - azsdk-python-data-tables/12.0.0b1 Python/3.8.4 (Windows-10-10.0.19041-SP0) x-ms-date: - - Thu, 20 Aug 2020 20:16:40 GMT + - Mon, 31 Aug 2020 22:27:33 GMT x-ms-version: - - '2019-07-07' + - '2019-02-02' method: DELETE uri: https://storagename.table.core.windows.net/Tables('uttable8fac1b18') response: @@ -77,13 +77,13 @@ interactions: content-length: - '0' date: - - Thu, 20 Aug 2020 20:16:40 GMT + - Mon, 31 Aug 2020 22:27:30 GMT server: - Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0 x-content-type-options: - nosniff x-ms-version: - - '2019-07-07' + - '2019-02-02' status: code: 204 message: No Content diff --git a/sdk/tables/azure-data-tables/tests/test_table.py b/sdk/tables/azure-data-tables/tests/test_table.py index 7ba338ac0d9c..e51e1c68651c 100644 --- a/sdk/tables/azure-data-tables/tests/test_table.py +++ b/sdk/tables/azure-data-tables/tests/test_table.py @@ -133,17 +133,39 @@ def test_create_table_fail_on_exist(self, resource_group, location, storage_acco # Arrange ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key) table_name = self._get_table_reference() - # btable_client = ts.get_table_client(table_name) # Act created = ts.create_table(table_name) with self.assertRaises(ResourceExistsError): ts.create_table(table_name) + print(created) # Assert - self.assertTrue(created) - # existing = list(ts.query_tables(query_options=QueryOptions(filter="TableName eq '{}'".format(table_name)))) - # self.assertEqual(existing[0], [table_name]) + self.assertIsNotNone(created) + ts.delete_table(table_name) + + @GlobalStorageAccountPreparer() + def test_create_table_if_exists(self, resource_group, location, storage_account, storage_account_key): + ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key) + table_name = self._get_table_reference() + + t0 = ts.create_table(table_name) + t1 = ts.create_table_if_not_exists(table_name) + + self.assertIsNotNone(t0) + self.assertIsNotNone(t1) + self.assertEqual(t0.table_name, t1.table_name) + ts.delete_table(table_name) + + @GlobalStorageAccountPreparer() + def test_create_table_if_exists_new_table(self, resource_group, location, storage_account, storage_account_key): + ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key) + table_name = self._get_table_reference() + + t = ts.create_table_if_not_exists(table_name) + + self.assertIsNotNone(t) + self.assertEqual(t.table_name, table_name) ts.delete_table(table_name) @GlobalStorageAccountPreparer() diff --git a/sdk/tables/azure-data-tables/tests/test_table_async.py b/sdk/tables/azure-data-tables/tests/test_table_async.py index 5fa577fec1f8..4a3d764de311 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_async.py @@ -73,6 +73,30 @@ async def test_create_table_fail_on_exist(self, resource_group, location, storag self.assertTrue(created) await ts.delete_table(table_name=table_name) + @GlobalStorageAccountPreparer() + async def test_create_table_if_exists(self, resource_group, location, storage_account, storage_account_key): + ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key) + table_name = self._get_table_reference() + + t0 = await ts.create_table(table_name) + t1 = await ts.create_table_if_not_exists(table_name) + + self.assertIsNotNone(t0) + self.assertIsNotNone(t1) + self.assertEqual(t0.table_name, t1.table_name) + await ts.delete_table(table_name) + + @GlobalStorageAccountPreparer() + async def test_create_table_if_exists_new_table(self, resource_group, location, storage_account, storage_account_key): + ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key) + table_name = self._get_table_reference() + + t = await ts.create_table_if_not_exists(table_name) + + self.assertIsNotNone(t) + self.assertEqual(t.table_name, table_name) + await ts.delete_table(table_name) + @GlobalStorageAccountPreparer() async def test_create_table_invalid_name(self, resource_group, location, storage_account, storage_account_key): # Arrange @@ -283,7 +307,7 @@ async def test_set_table_acl_with_empty_signed_identifiers(self, resource_group, @pytest.mark.skip("pending") @GlobalStorageAccountPreparer() - async def test_set_table_acl_with_empty_signed_identifier(self, resource_group, location, storage_account, + async def test_set_table_acl_with_none_signed_identifier(self, resource_group, location, storage_account, storage_account_key): # Arrange url = self.account_url(storage_account, "table") diff --git a/sdk/tables/azure-data-tables/tests/test_table_batch.py b/sdk/tables/azure-data-tables/tests/test_table_batch.py index ba8603f9703f..946280be374a 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_batch.py +++ b/sdk/tables/azure-data-tables/tests/test_table_batch.py @@ -163,7 +163,7 @@ def test_inferred_types(self): entity.test5 = EntityProperty(u"stringystring") entity.test6 = EntityProperty(3.14159) entity.test7 = EntityProperty(100) - entity.test8 = EntityProperty(10, EdmType.INT32) + entity.test8 = EntityProperty(2 ** 33) # Assert self.assertEqual(entity.test.type, EdmType.BOOLEAN) @@ -172,8 +172,8 @@ def test_inferred_types(self): self.assertEqual(entity.test4.type, EdmType.DATETIME) self.assertEqual(entity.test5.type, EdmType.STRING) self.assertEqual(entity.test6.type, EdmType.DOUBLE) - self.assertEqual(entity.test7.type, EdmType.INT64) - self.assertEqual(entity.test8.type, EdmType.INT32) + self.assertEqual(entity.test7.type, EdmType.INT32) + self.assertEqual(entity.test8.type, EdmType.INT64) @pytest.mark.skip("pending") diff --git a/sdk/tables/azure-data-tables/tests/test_table_entity.py b/sdk/tables/azure-data-tables/tests/test_table_entity.py index 149b70ddf49d..03b0775e155f 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_entity.py +++ b/sdk/tables/azure-data-tables/tests/test_table_entity.py @@ -160,7 +160,7 @@ def _assert_default_entity(self, entity, headers=None): self.assertEqual(entity['birthday'], datetime(1970, 10, 4, tzinfo=tzutc())) self.assertEqual(entity['binary'].value, b'binary') self.assertIsInstance(entity['other'], EntityProperty) - self.assertEqual(entity['other'].type, EdmType.INT64) + self.assertEqual(entity['other'].type, EdmType.INT32) self.assertEqual(entity['other'].value, 20) self.assertEqual(entity['clsid'], uuid.UUID('c9da6455-213d-42c9-9a79-3e9149a57833')) # self.assertTrue('metadata' in entity.odata) @@ -188,7 +188,7 @@ def _assert_default_entity_json_full_metadata(self, entity, headers=None): self.assertEqual(entity['birthday'], datetime(1970, 10, 4, tzinfo=tzutc())) self.assertEqual(entity['binary'].value, b'binary') self.assertIsInstance(entity['other'], EntityProperty) - self.assertEqual(entity['other'].type, EdmType.INT64) + self.assertEqual(entity['other'].type, EdmType.INT32) self.assertEqual(entity['other'].value, 20) self.assertEqual(entity['clsid'], uuid.UUID('c9da6455-213d-42c9-9a79-3e9149a57833')) # self.assertTrue('metadata' in entity.odata) @@ -222,7 +222,7 @@ def _assert_default_entity_json_no_metadata(self, entity, headers=None): self.assertTrue(entity['birthday'].endswith('00Z')) self.assertEqual(entity['binary'], b64encode(b'binary').decode('utf-8')) self.assertIsInstance(entity['other'], EntityProperty) - self.assertEqual(entity['other'].type, EdmType.INT64) + self.assertEqual(entity['other'].type, EdmType.INT32) self.assertEqual(entity['other'].value, 20) self.assertEqual(entity['clsid'], 'c9da6455-213d-42c9-9a79-3e9149a57833') # self.assertIsNone(entity.odata) @@ -273,7 +273,7 @@ def _assert_merged_entity(self, entity): self.assertEqual(entity.Birthday, datetime(1973, 10, 4, tzinfo=tzutc())) self.assertEqual(entity.birthday, datetime(1991, 10, 4, tzinfo=tzutc())) self.assertIsInstance(entity.other, EntityProperty) - self.assertEqual(entity.other.type, EdmType.INT64) + self.assertEqual(entity.other.type, EdmType.INT32) self.assertEqual(entity.other.value, 20) self.assertIsInstance(entity.clsid, uuid.UUID) self.assertEqual(str(entity.clsid), 'c9da6455-213d-42c9-9a79-3e9149a57833') diff --git a/sdk/tables/azure-data-tables/tests/test_table_entity_async.py b/sdk/tables/azure-data-tables/tests/test_table_entity_async.py index c962d20ddf3c..e5f012be9bd2 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_entity_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_entity_async.py @@ -161,7 +161,7 @@ def _assert_default_entity(self, entity, headers=None): self.assertEqual(entity['birthday'], datetime(1970, 10, 4, tzinfo=tzutc())) self.assertEqual(entity['binary'].value, b'binary') # TODO: added the ".value" portion, verify this is correct self.assertIsInstance(entity['other'], EntityProperty) - self.assertEqual(entity['other'].type, EdmType.INT64) + self.assertEqual(entity['other'].type, EdmType.INT32) self.assertEqual(entity['other'].value, 20) self.assertEqual(entity['clsid'], uuid.UUID('c9da6455-213d-42c9-9a79-3e9149a57833')) # self.assertTrue('metadata' in entity.odata) @@ -190,7 +190,7 @@ def _assert_default_entity_json_full_metadata(self, entity, headers=None): self.assertEqual(entity['birthday'], datetime(1970, 10, 4, tzinfo=tzutc())) self.assertEqual(entity['binary'].value, b'binary') self.assertIsInstance(entity['other'], EntityProperty) - self.assertEqual(entity['other'].type, EdmType.INT64) + self.assertEqual(entity['other'].type, EdmType.INT32) self.assertEqual(entity['other'].value, 20) self.assertEqual(entity['clsid'], uuid.UUID('c9da6455-213d-42c9-9a79-3e9149a57833')) # self.assertTrue('metadata' in entity.odata) @@ -225,7 +225,7 @@ def _assert_default_entity_json_no_metadata(self, entity, headers=None): self.assertTrue(entity['birthday'].endswith('00Z')) self.assertEqual(entity['binary'], b64encode(b'binary').decode('utf-8')) self.assertIsInstance(entity['other'], EntityProperty) - self.assertEqual(entity['other'].type, EdmType.INT64) + self.assertEqual(entity['other'].type, EdmType.INT32) self.assertEqual(entity['other'].value, 20) self.assertEqual(entity['clsid'], 'c9da6455-213d-42c9-9a79-3e9149a57833') # self.assertIsNone(entity.odata) @@ -275,7 +275,7 @@ def _assert_merged_entity(self, entity): self.assertEqual(entity.Birthday, datetime(1973, 10, 4, tzinfo=tzutc())) self.assertEqual(entity.birthday, datetime(1991, 10, 4, tzinfo=tzutc())) self.assertIsInstance(entity.other, EntityProperty) - self.assertEqual(entity.other.type, EdmType.INT64) + self.assertEqual(entity.other.type, EdmType.INT32) self.assertEqual(entity.other.value, 20) self.assertIsInstance(entity.clsid, uuid.UUID) self.assertEqual(str(entity.clsid), 'c9da6455-213d-42c9-9a79-3e9149a57833') diff --git a/sdk/tables/tests.yml b/sdk/tables/tests.yml index 86573bce751d..4f5aaf6fec1a 100644 --- a/sdk/tables/tests.yml +++ b/sdk/tables/tests.yml @@ -5,29 +5,12 @@ jobs: parameters: BuildTargetingString: azure-data-tables ServiceDirectory: tables + AllocateResourceGroup: 'false' EnvVars: - STORAGE_ACCOUNT_NAME: $(python-storage-storage-account-name) - STORAGE_ACCOUNT_KEY: $(python-storage-storage-account-key) - STORAGE_DATA_LAKE_ACCOUNT_NAME: $(python-storage-data-lake-account-name) - STORAGE_DATA_LAKE_ACCOUNT_KEY: $(python-storage-data-lake-account-key) - BLOB_STORAGE_ACCOUNT_NAME: $(python-storage-blob-storage-account-name) - BLOB_STORAGE_ACCOUNT_KEY: $(python-storage-blob-storage-account-key) - REMOTE_STORAGE_ACCOUNT_NAME: $(python-storage-remote-storage-account-name) - REMOTE_STORAGE_ACCOUNT_KEY: $(python-storage-remote-storage-account-key) - PREMIUM_STORAGE_ACCOUNT_NAME: $(python-storage-premium-storage-account-name) - PREMIUM_STORAGE_ACCOUNT_KEY: $(python-storage-premium-storage-account-key) - OAUTH_STORAGE_ACCOUNT_NAME: $(python-storage-oauth-storage-account-name) - OAUTH_STORAGE_ACCOUNT_KEY: $(python-storage-oauth-storage-account-key) - ACTIVE_DIRECTORY_APPLICATION_ID: $(aad-azure-sdk-test-client-id) - ACTIVE_DIRECTORY_APPLICATION_SECRET: $(aad-azure-sdk-test-client-secret) - ACTIVE_DIRECTORY_TENANT_ID: $(aad-azure-sdk-test-tenant-id) - CONNECTION_STRING: $(python-storage-blob-connection-string) - BLOB_CONNECTION_STRING: $(python-storage-blob-connection-string) - PREMIUM_CONNECTION_STRING: $(python-storage-premium-connection-string) - TEST_MODE: 'RunLiveNoRecord' - AZURE_SKIP_LIVE_RECORDING: 'True' - AZURE_TEST_RUN_LIVE: 'true' AZURE_TENANT_ID: $(aad-azure-sdk-test-tenant-id) AZURE_SUBSCRIPTION_ID: $(azure-subscription-id) - AZURE_CLIENT_SECRET: $(aad-azure-sdk-test-client-secret) AZURE_CLIENT_ID: $(aad-azure-sdk-test-client-id) + AZURE_CLIENT_SECRET: $(aad-azure-sdk-test-client-secret) + TEST_MODE: 'RunLiveNoRecord' + AZURE_SKIP_LIVE_RECORDING: 'True' + AZURE_TEST_RUN_LIVE: 'true' diff --git a/sdk/textanalytics/azure-ai-textanalytics/CHANGELOG.md b/sdk/textanalytics/azure-ai-textanalytics/CHANGELOG.md index 372b10411b81..b2ceff0d8706 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/CHANGELOG.md +++ b/sdk/textanalytics/azure-ai-textanalytics/CHANGELOG.md @@ -1,16 +1,19 @@ # Release History -## 5.0.1 (Unreleased) +## 5.1.0b1 (Unreleased) **New features** - We are now targeting the service's v3.1-preview.1 API as the default. If you would like to still use version v3.0 of the service, pass in `v3.0` to the kwarg `api_version` when creating your TextAnalyticsClient - We have added an API `recognize_pii_entities` which returns entities containing personal information for a batch of documents. Only available for API version v3.1-preview.1 and up. + - In API version v3.1-preview.2 and up, the redacted text of the document is returned on the top-level result object `RecognizePiiEntitiesResult` through property `redacted_text`. - Added `offset` and `length` properties for `CategorizedEntity`, `SentenceSentiment`, and `LinkedEntityMatch`. These properties are only available for API versions v3.1-preview.1 and up. - `length` is the number of characters in the text of these models - `offset` is the offset of the text from the start of the document - We now have added support for opinion mining. To use this feature, you need to make sure you are using the service's v3.1-preview.1 API. To get this support pass `show_opinion_mining` as True when calling the `analyze_sentiment` endpoint +- Add property `bing_entity_search_api_id` to the `LinkedEntity` class. This property is only available for v3.1-preview.2 and up, and it is to be +used in conjunction with the Bing Entity Search API to fetch additional relevant information about the returned entity. ## 5.0.0 (2020-07-27) diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/__init__.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/__init__.py index 476f9842a066..ef3d19429fd8 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/__init__.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/__init__.py @@ -31,6 +31,7 @@ OpinionSentiment, RecognizePiiEntitiesResult, PiiEntity, + PiiEntityDomainType, ) __all__ = [ @@ -59,6 +60,7 @@ 'OpinionSentiment', 'RecognizePiiEntitiesResult', 'PiiEntity', + 'PiiEntityDomainType', ] __version__ = VERSION diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_base_client.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_base_client.py index c269772d87ff..8d60ff8dbf9d 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_base_client.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_base_client.py @@ -15,6 +15,10 @@ class TextAnalyticsApiVersion(str, Enum): #: this is the default version V3_1_PREVIEW_1 = "v3.1-preview.1" + + # 3.1-preview.2 is not yet the default version since we don't have a + # reliable endpoint + V3_1_PREVIEW_2 = "v3.1-preview.2" V3_0 = "v3.0" def _authentication_policy(credential): diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_models.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_models.py index e9733e8fec25..f9aa26334e0e 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_models.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_models.py @@ -1,9 +1,10 @@ -# coding=utf-8 +# coding=utf-8 pylint: disable=too-many-lines # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ import re +from enum import Enum from ._generated.models import ( LanguageInput, MultiLanguageInput, @@ -64,6 +65,10 @@ def get(self, key, default=None): return self.__dict__[key] return default +class PiiEntityDomainType(str, Enum): + """The different domains of PII entities that users can filter by""" + PROTECTED_HEALTH_INFORMATION = "PHI" # See https://aka.ms/tanerpii for more information. + class DetectedLanguage(DictMixin): """DetectedLanguage contains the predicted language found in text, @@ -141,6 +146,8 @@ class RecognizePiiEntitiesResult(DictMixin): :ivar entities: Recognized PII entities in the document. :vartype entities: list[~azure.ai.textanalytics.PiiEntity] + :ivar str redacted_text: Returns the text of the input document with all of the PII information + redacted out. Only returned for API versions v3.1-preview.2 and up. :ivar warnings: Warnings encountered while processing document. Results will still be returned if there are warnings, but they may not be fully accurate. :vartype warnings: list[~azure.ai.textanalytics.TextAnalyticsWarning] @@ -150,18 +157,28 @@ class RecognizePiiEntitiesResult(DictMixin): ~azure.ai.textanalytics.TextDocumentStatistics :ivar bool is_error: Boolean check for error item when iterating over list of results. Always False for an instance of a RecognizePiiEntitiesResult. + .. versionadded:: v3.1-preview.2 + The *redacted_text* parameter. """ def __init__(self, **kwargs): self.id = kwargs.get("id", None) self.entities = kwargs.get("entities", None) + self.redacted_text = kwargs.get("redacted_text", None) self.warnings = kwargs.get("warnings", []) self.statistics = kwargs.get("statistics", None) self.is_error = False def __repr__(self): - return "RecognizePiiEntitiesResult(id={}, entities={}, warnings={}, statistics={}, is_error={})" \ - .format(self.id, repr(self.entities), repr(self.warnings), repr(self.statistics), self.is_error)[:1024] + return "RecognizePiiEntitiesResult(id={}, entities={}, redacted_text={}, warnings={}, " \ + "statistics={}, is_error={})" .format( + self.id, + repr(self.entities), + self.redacted_text, + repr(self.warnings), + repr(self.statistics), + self.is_error + )[:1024] class DetectLanguageResult(DictMixin): @@ -209,12 +226,14 @@ class CategorizedEntity(DictMixin): :ivar subcategory: Entity subcategory, such as Age/Year/TimeRange etc :vartype subcategory: str :ivar int offset: The entity text offset from the start of the document. - Returned in unicode code points. Only returned for api versions v3.1-preview.1 and up. + Returned in unicode code points. Only returned for API versions v3.1-preview.1 and up. :ivar int length: The length of the entity text. Returned - in unicode code points. Only returned for api versions v3.1-preview.1 and up. + in unicode code points. Only returned for API versions v3.1-preview.1 and up. :ivar confidence_score: Confidence score between 0 and 1 of the extracted entity. :vartype confidence_score: float + .. versionadded:: v3.1-preview.1 + The *offset* and *length* properties. """ def __init__(self, **kwargs): @@ -611,6 +630,11 @@ class LinkedEntity(DictMixin): :ivar data_source: Data source used to extract entity linking, such as Wiki/Bing etc. :vartype data_source: str + :ivar str bing_entity_search_api_id: Bing unique identifier of the recognized entity. Use in conjunction + with the Bing Entity Search SDK to fetch additional relevant information. Only + available for API version v3.1-preview.2 and up. + .. versionadded:: v3.1-preview.2 + The *bing_entity_search_api_id* property. """ def __init__(self, **kwargs): @@ -620,9 +644,11 @@ def __init__(self, **kwargs): self.data_source_entity_id = kwargs.get("data_source_entity_id", None) self.url = kwargs.get("url", None) self.data_source = kwargs.get("data_source", None) + self.bing_entity_search_api_id = kwargs.get("bing_entity_search_api_id", None) @classmethod def _from_generated(cls, entity): + bing_entity_search_api_id = entity.bing_id if hasattr(entity, "bing_id") else None return cls( name=entity.name, matches=[LinkedEntityMatch._from_generated(e) for e in entity.matches], # pylint: disable=protected-access @@ -630,12 +656,20 @@ def _from_generated(cls, entity): data_source_entity_id=entity.id, url=entity.url, data_source=entity.data_source, + bing_entity_search_api_id=bing_entity_search_api_id, ) def __repr__(self): return "LinkedEntity(name={}, matches={}, language={}, data_source_entity_id={}, url={}, " \ - "data_source={})".format(self.name, repr(self.matches), self.language, self.data_source_entity_id, - self.url, self.data_source)[:1024] + "data_source={}, bing_entity_search_api_id={})".format( + self.name, + repr(self.matches), + self.language, + self.data_source_entity_id, + self.url, + self.data_source, + self.bing_entity_search_api_id, + )[:1024] class LinkedEntityMatch(DictMixin): @@ -649,10 +683,12 @@ class LinkedEntityMatch(DictMixin): :vartype confidence_score: float :ivar text: Entity text as appears in the request. :ivar int offset: The linked entity match text offset from the start of the document. - Returned in unicode code points. Only returned for api versions v3.1-preview.1 and up. + Returned in unicode code points. Only returned for API versions v3.1-preview.1 and up. :ivar int length: The length of the linked entity match text. Returned - in unicode code points. Only returned for api versions v3.1-preview.1 and up. + in unicode code points. Only returned for API versions v3.1-preview.1 and up. :vartype text: str + .. versionadded:: v3.1-preview.1 + The *offset* and *length* properties. """ def __init__(self, **kwargs): @@ -761,15 +797,18 @@ class SentenceSentiment(DictMixin): :vartype confidence_scores: ~azure.ai.textanalytics.SentimentConfidenceScores :ivar int offset: The sentence offset from the start of the document. Returned - in unicode code points. Only returned for api versions v3.1-preview.1 and up. + in unicode code points. Only returned for API versions v3.1-preview.1 and up. :ivar int length: The length of the sentence. Returned - in unicode code points. Only returned for api versions v3.1-preview.1 and up. + in unicode code points. Only returned for API versions v3.1-preview.1 and up. :ivar mined_opinions: The list of opinions mined from this sentence. For example in "The food is good, but the service is bad", we would mind these two opinions "food is good", "service is bad". Only returned - if `show_opinion_mining` is set to True in the call to `analyze_sentiment`. + if `show_opinion_mining` is set to True in the call to `analyze_sentiment` and + api version is v3.1-preview.1 and up. :vartype mined_opinions: list[~azure.ai.textanalytics.MinedOpinion] + .. versionadded:: v3.1-preview.1 + The *offset*, *length*, and *mined_opinions* properties. """ def __init__(self, **kwargs): diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_response_handlers.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_response_handlers.py index b07421d7847b..559fbf17cc5f 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_response_handlers.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_response_handlers.py @@ -134,6 +134,7 @@ def pii_entities_result(entity, results): # pylint: disable=unused-argument return RecognizePiiEntitiesResult( id=entity.id, entities=[PiiEntity._from_generated(e) for e in entity.entities], # pylint: disable=protected-access + redacted_text=entity.redacted_text if hasattr(entity, "redacted_text") else None, warnings=[TextAnalyticsWarning._from_generated(w) for w in entity.warnings], # pylint: disable=protected-access statistics=TextDocumentStatistics._from_generated(entity.statistics), # pylint: disable=protected-access ) diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_text_analytics_client.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_text_analytics_client.py index 8c636e18ae92..7c68034c9de8 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_text_analytics_client.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_text_analytics_client.py @@ -260,6 +260,10 @@ def recognize_pii_entities( # type: ignore be used for scoring, e.g. "latest", "2019-10-01". If a model-version is not specified, the API will default to the latest, non-preview version. :keyword bool show_stats: If set to true, response will contain document level statistics. + :keyword domain_filter: Filters the response entities to ones only included in the specified domain. + I.e., if set to 'PHI', will only return entities in the Protected Healthcare Information domain. + See https://aka.ms/tanerpii for more information. + :paramtype domain_filter: str or ~azure.ai.textanalytics.PiiEntityDomainType :return: The combined list of :class:`~azure.ai.textanalytics.RecognizePiiEntitiesResult` and :class:`~azure.ai.textanalytics.DocumentError` in the order the original documents were passed in. @@ -281,6 +285,7 @@ def recognize_pii_entities( # type: ignore docs = _validate_input(documents, "language", language) model_version = kwargs.pop("model_version", None) show_stats = kwargs.pop("show_stats", False) + domain_filter = kwargs.pop("domain_filter", None) if self._string_code_unit: kwargs.update({"string_index_type": self._string_code_unit}) try: @@ -288,6 +293,7 @@ def recognize_pii_entities( # type: ignore documents=docs, model_version=model_version, show_stats=show_stats, + domain=domain_filter, cls=kwargs.pop("cls", pii_entities_result), **kwargs ) diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_version.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_version.py index 715b122ebe53..40d5e79e323a 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_version.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/_version.py @@ -4,4 +4,4 @@ # Licensed under the MIT License. # ------------------------------------ -VERSION = "5.0.1" +VERSION = "5.1.0b1" diff --git a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/aio/_text_analytics_client_async.py b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/aio/_text_analytics_client_async.py index ffa9cd77d6e2..f7c6290665ba 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/aio/_text_analytics_client_async.py +++ b/sdk/textanalytics/azure-ai-textanalytics/azure/ai/textanalytics/aio/_text_analytics_client_async.py @@ -262,6 +262,10 @@ async def recognize_pii_entities( # type: ignore be used for scoring, e.g. "latest", "2019-10-01". If a model-version is not specified, the API will default to the latest, non-preview version. :keyword bool show_stats: If set to true, response will contain document level statistics. + :keyword domain_filter: Filters the response entities to ones only included in the specified domain. + I.e., if set to 'PHI', will only return entities in the Protected Healthcare Information domain. + See https://aka.ms/tanerpii for more information. + :paramtype domain_filter: str or ~azure.ai.textanalytics.PiiEntityDomainType :return: The combined list of :class:`~azure.ai.textanalytics.RecognizePiiEntitiesResult` and :class:`~azure.ai.textanalytics.DocumentError` in the order the original documents were passed in. @@ -283,6 +287,8 @@ async def recognize_pii_entities( # type: ignore docs = _validate_input(documents, "language", language) model_version = kwargs.pop("model_version", None) show_stats = kwargs.pop("show_stats", False) + domain_filter = kwargs.pop("domain_filter", None) + if self._string_code_unit: kwargs.update({"string_index_type": self._string_code_unit}) try: @@ -290,6 +296,7 @@ async def recognize_pii_entities( # type: ignore documents=docs, model_version=model_version, show_stats=show_stats, + domain=domain_filter, cls=kwargs.pop("cls", pii_entities_result), **kwargs ) diff --git a/sdk/textanalytics/azure-ai-textanalytics/samples/async_samples/sample_analyze_sentiment_with_opinion_mining_async.py b/sdk/textanalytics/azure-ai-textanalytics/samples/async_samples/sample_analyze_sentiment_with_opinion_mining_async.py index 338432cb9530..fc0811c6f37b 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/samples/async_samples/sample_analyze_sentiment_with_opinion_mining_async.py +++ b/sdk/textanalytics/azure-ai-textanalytics/samples/async_samples/sample_analyze_sentiment_with_opinion_mining_async.py @@ -24,6 +24,57 @@ Set the environment variables with your own values before running the sample: 1) AZURE_TEXT_ANALYTICS_ENDPOINT - the endpoint to your Cognitive Services resource. 2) AZURE_TEXT_ANALYTICS_KEY - your Text Analytics subscription key + +OUTPUT: + In this sample we will be combing through the reviews of a potential hotel to stay at: Hotel Foo. + I first found a handful of reviews for Hotel Foo. Let's see if I want to stay here. + + + Let's see how many positive and negative reviews of this hotel I have right now + ...We have 3 positive reviews and 2 negative reviews. + + Looks more positive than negative, but still pretty mixed, so I'm going to drill deeper into the opinions of individual aspects of this hotel + + In order to do that, I'm going to sort them based on whether these opinions are positive, mixed, or negative + + + Let's look at the 7 positive opinions users have expressed for aspects of this hotel + ...Reviewers have the following opinions for the overall positive 'concierge' aspect of the hotel + ......'positive' opinion 'nice' + ...Reviewers have the following opinions for the overall positive 'AC' aspect of the hotel + ......'positive' opinion 'good' + ......'positive' opinion 'quiet' + ...Reviewers have the following opinions for the overall positive 'breakfast' aspect of the hotel + ......'positive' opinion 'good' + ...Reviewers have the following opinions for the overall positive 'hotel' aspect of the hotel + ......'positive' opinion 'good' + ...Reviewers have the following opinions for the overall positive 'breakfast' aspect of the hotel + ......'positive' opinion 'nice' + ...Reviewers have the following opinions for the overall positive 'shuttle service' aspect of the hotel + ......'positive' opinion 'loved' + ...Reviewers have the following opinions for the overall positive 'view' aspect of the hotel + ......'positive' opinion 'great' + ......'positive' opinion 'unobstructed' + + + Now let's look at the 1 mixed opinions users have expressed for aspects of this hotel + ...Reviewers have the following opinions for the overall mixed 'rooms' aspect of the hotel + ......'positive' opinion 'beautiful' + ......'negative' opinion 'dirty' + + + Finally, let's see the 4 negative opinions users have expressed for aspects of this hotel + ...Reviewers have the following opinions for the overall negative 'food' aspect of the hotel + ......'negative' opinion 'unacceptable' + ...Reviewers have the following opinions for the overall negative 'service' aspect of the hotel + ......'negative' opinion 'unacceptable' + ...Reviewers have the following opinions for the overall negative 'elevator' aspect of the hotel + ......'negative' opinion 'broken' + ...Reviewers have the following opinions for the overall negative 'toilet' aspect of the hotel + ......'negative' opinion 'smelly' + + + Looking at the breakdown, even though there were more positive opinions of this hotel, I care the most about the food and the toilets in a hotel, so I will be staying elsewhere """ import os diff --git a/sdk/textanalytics/azure-ai-textanalytics/samples/sample_analyze_sentiment_with_opinion_mining.py b/sdk/textanalytics/azure-ai-textanalytics/samples/sample_analyze_sentiment_with_opinion_mining.py index 1b2924134bef..3cd161b93b8a 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/samples/sample_analyze_sentiment_with_opinion_mining.py +++ b/sdk/textanalytics/azure-ai-textanalytics/samples/sample_analyze_sentiment_with_opinion_mining.py @@ -24,6 +24,57 @@ Set the environment variables with your own values before running the sample: 1) AZURE_TEXT_ANALYTICS_ENDPOINT - the endpoint to your Cognitive Services resource. 2) AZURE_TEXT_ANALYTICS_KEY - your Text Analytics subscription key + +OUTPUT: + In this sample we will be combing through the reviews of a potential hotel to stay at: Hotel Foo. + I first found a handful of reviews for Hotel Foo. Let's see if I want to stay here. + + + Let's see how many positive and negative reviews of this hotel I have right now + ...We have 3 positive reviews and 2 negative reviews. + + Looks more positive than negative, but still pretty mixed, so I'm going to drill deeper into the opinions of individual aspects of this hotel + + In order to do that, I'm going to sort them based on whether these opinions are positive, mixed, or negative + + + Let's look at the 7 positive opinions users have expressed for aspects of this hotel + ...Reviewers have the following opinions for the overall positive 'concierge' aspect of the hotel + ......'positive' opinion 'nice' + ...Reviewers have the following opinions for the overall positive 'AC' aspect of the hotel + ......'positive' opinion 'good' + ......'positive' opinion 'quiet' + ...Reviewers have the following opinions for the overall positive 'breakfast' aspect of the hotel + ......'positive' opinion 'good' + ...Reviewers have the following opinions for the overall positive 'hotel' aspect of the hotel + ......'positive' opinion 'good' + ...Reviewers have the following opinions for the overall positive 'breakfast' aspect of the hotel + ......'positive' opinion 'nice' + ...Reviewers have the following opinions for the overall positive 'shuttle service' aspect of the hotel + ......'positive' opinion 'loved' + ...Reviewers have the following opinions for the overall positive 'view' aspect of the hotel + ......'positive' opinion 'great' + ......'positive' opinion 'unobstructed' + + + Now let's look at the 1 mixed opinions users have expressed for aspects of this hotel + ...Reviewers have the following opinions for the overall mixed 'rooms' aspect of the hotel + ......'positive' opinion 'beautiful' + ......'negative' opinion 'dirty' + + + Finally, let's see the 4 negative opinions users have expressed for aspects of this hotel + ...Reviewers have the following opinions for the overall negative 'food' aspect of the hotel + ......'negative' opinion 'unacceptable' + ...Reviewers have the following opinions for the overall negative 'service' aspect of the hotel + ......'negative' opinion 'unacceptable' + ...Reviewers have the following opinions for the overall negative 'elevator' aspect of the hotel + ......'negative' opinion 'broken' + ...Reviewers have the following opinions for the overall negative 'toilet' aspect of the hotel + ......'negative' opinion 'smelly' + + + Looking at the breakdown, even though there were more positive opinions of this hotel, I care the most about the food and the toilets in a hotel, so I will be staying elsewhere """ import os diff --git a/sdk/textanalytics/azure-ai-textanalytics/setup.py b/sdk/textanalytics/azure-ai-textanalytics/setup.py index 8eca54ff849d..46f83113c5dd 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/setup.py +++ b/sdk/textanalytics/azure-ai-textanalytics/setup.py @@ -59,7 +59,7 @@ author_email='azpysdkhelp@microsoft.com', url='https://github.com/Azure/azure-sdk-for-python', classifiers=[ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_linked_entities.test_bing_id.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_linked_entities.test_bing_id.yaml new file mode 100644 index 000000000000..537c216e5a8b --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_linked_entities.test_bing_id.yaml @@ -0,0 +1,47 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "Microsoft was founded by Bill Gates + and Paul Allen", "language": "en"}]}' + headers: + Accept: + - application/json, text/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '108' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://cognitiveusw2dev.azure-api.net/text/analytics/v3.1-preview.2/entities/linking?showStats=false&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"id":"0","entities":[{"bingId":"0d47c987-0042-5576-15e8-97af601614fa","name":"Bill + Gates","matches":[{"text":"Bill Gates","offset":25,"length":10,"confidenceScore":0.52}],"language":"en","id":"Bill + Gates","url":"https://en.wikipedia.org/wiki/Bill_Gates","dataSource":"Wikipedia"},{"bingId":"df2c4376-9923-6a54-893f-2ee5a5badbc7","name":"Paul + Allen","matches":[{"text":"Paul Allen","offset":40,"length":10,"confidenceScore":0.54}],"language":"en","id":"Paul + Allen","url":"https://en.wikipedia.org/wiki/Paul_Allen","dataSource":"Wikipedia"},{"bingId":"a093e9b9-90f5-a3d5-c4b8-5855e1b01f85","name":"Microsoft","matches":[{"text":"Microsoft","offset":0,"length":9,"confidenceScore":0.49}],"language":"en","id":"Microsoft","url":"https://en.wikipedia.org/wiki/Microsoft","dataSource":"Wikipedia"}],"warnings":[]}],"errors":[],"modelVersion":"2020-02-01"}' + headers: + apim-request-id: + - 34b34e81-fcc2-4c1e-85b2-116f85196a4c + content-type: + - application/json; charset=utf-8 + csp-billing-usage: + - CognitiveServices.TextAnalytics.BatchScoring=1 + date: + - Mon, 31 Aug 2020 18:48:40 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '27' + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_linked_entities_async.test_bing_id.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_linked_entities_async.test_bing_id.yaml new file mode 100644 index 000000000000..2c4123289eb0 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_linked_entities_async.test_bing_id.yaml @@ -0,0 +1,36 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "Microsoft was founded by Bill Gates + and Paul Allen", "language": "en"}]}' + headers: + Accept: + - application/json, text/json + Content-Length: + - '108' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://cognitiveusw2dev.azure-api.net/text/analytics/v3.1-preview.2/entities/linking?showStats=false&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"id":"0","entities":[{"bingId":"0d47c987-0042-5576-15e8-97af601614fa","name":"Bill + Gates","matches":[{"text":"Bill Gates","offset":25,"length":10,"confidenceScore":0.52}],"language":"en","id":"Bill + Gates","url":"https://en.wikipedia.org/wiki/Bill_Gates","dataSource":"Wikipedia"},{"bingId":"df2c4376-9923-6a54-893f-2ee5a5badbc7","name":"Paul + Allen","matches":[{"text":"Paul Allen","offset":40,"length":10,"confidenceScore":0.54}],"language":"en","id":"Paul + Allen","url":"https://en.wikipedia.org/wiki/Paul_Allen","dataSource":"Wikipedia"},{"bingId":"a093e9b9-90f5-a3d5-c4b8-5855e1b01f85","name":"Microsoft","matches":[{"text":"Microsoft","offset":0,"length":9,"confidenceScore":0.49}],"language":"en","id":"Microsoft","url":"https://en.wikipedia.org/wiki/Microsoft","dataSource":"Wikipedia"}],"warnings":[]}],"errors":[],"modelVersion":"2020-02-01"}' + headers: + apim-request-id: 70ab796e-3da1-4a55-86b4-16c4b19a97a8 + content-type: application/json; charset=utf-8 + csp-billing-usage: CognitiveServices.TextAnalytics.BatchScoring=1 + date: Mon, 31 Aug 2020 18:48:41 GMT + strict-transport-security: max-age=31536000; includeSubDomains; preload + transfer-encoding: chunked + x-content-type-options: nosniff + x-envoy-upstream-service-time: '26' + status: + code: 200 + message: OK + url: https://cognitiveusw2dev.azure-api.net/text/analytics/v3.1-preview.2/entities/linking?showStats=false&stringIndexType=UnicodeCodePoint +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_phi_domain_filter.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_phi_domain_filter.yaml new file mode 100644 index 000000000000..ee650032c7a0 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_phi_domain_filter.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "I work at Microsoft and my phone number + is 333-333-3333", "language": "en"}]}' + headers: + Accept: + - application/json, text/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '113' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://westus2.api.cognitive.microsoft.com/text/analytics/v3.1-preview.1/entities/recognition/pii?showStats=false&domain=PHI&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"id":"0","entities":[{"text":"333-333-3333","category":"Phone + Number","offset":43,"length":12,"confidenceScore":0.8}],"warnings":[]}],"errors":[],"modelVersion":"2020-07-01"}' + headers: + apim-request-id: + - c2319b95-6fd2-46c9-80e3-06c8f2701825 + content-type: + - application/json; charset=utf-8 + csp-billing-usage: + - CognitiveServices.TextAnalytics.BatchScoring=1 + date: + - Mon, 31 Aug 2020 20:32:54 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '79' + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_redacted_text.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_redacted_text.yaml new file mode 100644 index 000000000000..da525c49fea0 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_redacted_text.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "My SSN is 859-98-0987.", "language": + "en"}]}' + headers: + Accept: + - application/json, text/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '80' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://cognitiveusw2dev.azure-api.net/text/analytics/v3.1-preview.2/entities/recognition/pii?showStats=false&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"redactedText":"My SSN is ***********.","id":"0","entities":[{"text":"859-98-0987","category":"U.S. + Social Security Number (SSN)","offset":10,"length":11,"confidenceScore":0.65}],"warnings":[]}],"errors":[],"modelVersion":"2020-07-01"}' + headers: + apim-request-id: + - c5ba8c84-0e46-471a-b4c8-f02c411c20ec + content-type: + - application/json; charset=utf-8 + csp-billing-usage: + - CognitiveServices.TextAnalytics.BatchScoring=1 + date: + - Mon, 31 Aug 2020 20:15:43 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '78' + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_redacted_text_v3_1_preview_1.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_redacted_text_v3_1_preview_1.yaml new file mode 100644 index 000000000000..621c6af4efd5 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities.test_redacted_text_v3_1_preview_1.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "My SSN is 859-98-0987.", "language": + "en"}]}' + headers: + Accept: + - application/json, text/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '80' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://westus2.api.cognitive.microsoft.com/text/analytics/v3.1-preview.1/entities/recognition/pii?showStats=false&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"id":"0","entities":[{"text":"859-98-0987","category":"U.S. + Social Security Number (SSN)","offset":10,"length":11,"confidenceScore":0.65}],"warnings":[]}],"errors":[],"modelVersion":"2020-07-01"}' + headers: + apim-request-id: + - 4ae026d1-15d1-4d77-8913-46922e72d7cb + content-type: + - application/json; charset=utf-8 + csp-billing-usage: + - CognitiveServices.TextAnalytics.BatchScoring=1 + date: + - Mon, 31 Aug 2020 19:58:17 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '68' + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_phi_domain_filter.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_phi_domain_filter.yaml new file mode 100644 index 000000000000..7395d5ac2e41 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_phi_domain_filter.yaml @@ -0,0 +1,33 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "I work at Microsoft and my phone number + is 333-333-3333", "language": "en"}]}' + headers: + Accept: + - application/json, text/json + Content-Length: + - '113' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://westus2.api.cognitive.microsoft.com/text/analytics/v3.1-preview.1/entities/recognition/pii?showStats=false&domain=PHI&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"id":"0","entities":[{"text":"333-333-3333","category":"Phone + Number","offset":43,"length":12,"confidenceScore":0.8}],"warnings":[]}],"errors":[],"modelVersion":"2020-07-01"}' + headers: + apim-request-id: 9265752d-3262-4dbb-94d6-be26889e3db9 + content-type: application/json; charset=utf-8 + csp-billing-usage: CognitiveServices.TextAnalytics.BatchScoring=1 + date: Mon, 31 Aug 2020 20:32:55 GMT + strict-transport-security: max-age=31536000; includeSubDomains; preload + transfer-encoding: chunked + x-content-type-options: nosniff + x-envoy-upstream-service-time: '82' + status: + code: 200 + message: OK + url: https://westus2.api.cognitive.microsoft.com//text/analytics/v3.1-preview.1/entities/recognition/pii?showStats=false&domain=PHI&stringIndexType=UnicodeCodePoint +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_redacted_text.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_redacted_text.yaml new file mode 100644 index 000000000000..df78eca47113 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_redacted_text.yaml @@ -0,0 +1,33 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "My SSN is 859-98-0987.", "language": + "en"}]}' + headers: + Accept: + - application/json, text/json + Content-Length: + - '80' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://cognitiveusw2dev.azure-api.net/text/analytics/v3.1-preview.2/entities/recognition/pii?showStats=false&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"redactedText":"My SSN is ***********.","id":"0","entities":[{"text":"859-98-0987","category":"U.S. + Social Security Number (SSN)","offset":10,"length":11,"confidenceScore":0.65}],"warnings":[]}],"errors":[],"modelVersion":"2020-07-01"}' + headers: + apim-request-id: dc638432-dc71-4f52-aadb-829c2dfd1935 + content-type: application/json; charset=utf-8 + csp-billing-usage: CognitiveServices.TextAnalytics.BatchScoring=1 + date: Mon, 31 Aug 2020 20:15:43 GMT + strict-transport-security: max-age=31536000; includeSubDomains; preload + transfer-encoding: chunked + x-content-type-options: nosniff + x-envoy-upstream-service-time: '80' + status: + code: 200 + message: OK + url: https://cognitiveusw2dev.azure-api.net//text/analytics/v3.1-preview.2/entities/recognition/pii?showStats=false&stringIndexType=UnicodeCodePoint +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_redacted_text_v3_1_preview_1.yaml b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_redacted_text_v3_1_preview_1.yaml new file mode 100644 index 000000000000..55b101e7b851 --- /dev/null +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/recordings/test_recognize_pii_entities_async.test_redacted_text_v3_1_preview_1.yaml @@ -0,0 +1,33 @@ +interactions: +- request: + body: '{"documents": [{"id": "0", "text": "My SSN is 859-98-0987.", "language": + "en"}]}' + headers: + Accept: + - application/json, text/json + Content-Length: + - '80' + Content-Type: + - application/json + User-Agent: + - azsdk-python-ai-textanalytics/5.0.1 Python/3.8.5 (macOS-10.13.6-x86_64-i386-64bit) + method: POST + uri: https://westus2.api.cognitive.microsoft.com/text/analytics/v3.1-preview.1/entities/recognition/pii?showStats=false&stringIndexType=UnicodeCodePoint + response: + body: + string: '{"documents":[{"id":"0","entities":[{"text":"859-98-0987","category":"U.S. + Social Security Number (SSN)","offset":10,"length":11,"confidenceScore":0.65}],"warnings":[]}],"errors":[],"modelVersion":"2020-07-01"}' + headers: + apim-request-id: eeda4dd4-74dd-4e54-88cb-5a0352f065cf + content-type: application/json; charset=utf-8 + csp-billing-usage: CognitiveServices.TextAnalytics.BatchScoring=1 + date: Mon, 31 Aug 2020 19:58:17 GMT + strict-transport-security: max-age=31536000; includeSubDomains; preload + transfer-encoding: chunked + x-content-type-options: nosniff + x-envoy-upstream-service-time: '106' + status: + code: 200 + message: OK + url: https://westus2.api.cognitive.microsoft.com//text/analytics/v3.1-preview.1/entities/recognition/pii?showStats=false&stringIndexType=UnicodeCodePoint +version: 1 diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities.py b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities.py index 2ca6a82027d7..3701e1d58e71 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities.py +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities.py @@ -3,7 +3,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ - +import os import pytest import platform import functools @@ -586,3 +586,17 @@ def test_string_index_type_not_fail_v3(self, client): # make sure that the addition of the string_index_type kwarg for v3.1-preview.1 doesn't # cause v3.0 calls to fail client.recognize_linked_entities(["please don't fail"]) + + # currently only have this as playback since the dev endpoint is unreliable + @pytest.mark.playback_test_only + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer(client_kwargs={ + "api_version": TextAnalyticsApiVersion.V3_1_PREVIEW_2, + "text_analytics_account_key": os.environ.get('AZURE_TEXT_ANALYTICS_KEY'), + "text_analytics_account": "https://cognitiveusw2dev.azure-api.net/" + }) + def test_bing_id(self, client): + result = client.recognize_linked_entities(["Microsoft was founded by Bill Gates and Paul Allen"]) + for doc in result: + for entity in doc.entities: + assert entity.bing_entity_search_api_id # this checks if it's None and if it's empty diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities_async.py b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities_async.py index e055f9178a24..4a2488da0c80 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities_async.py +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_linked_entities_async.py @@ -3,7 +3,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ - +import os import pytest import platform import functools @@ -622,3 +622,17 @@ async def test_string_index_type_not_fail_v3(self, client): # make sure that the addition of the string_index_type kwarg for v3.1-preview.1 doesn't # cause v3.0 calls to fail await client.recognize_linked_entities(["please don't fail"]) + + # currently only have this as playback since the dev endpoint is unreliable + @pytest.mark.playback_test_only + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer(client_kwargs={ + "api_version": TextAnalyticsApiVersion.V3_1_PREVIEW_2, + "text_analytics_account_key": os.environ.get('AZURE_TEXT_ANALYTICS_KEY'), + "text_analytics_account": "https://cognitiveusw2dev.azure-api.net/" + }) + async def test_bing_id(self, client): + result = await client.recognize_linked_entities(["Microsoft was founded by Bill Gates and Paul Allen"]) + for doc in result: + for entity in doc.entities: + assert entity.bing_entity_search_api_id # this checks if it's None and if it's empty diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities.py b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities.py index 736f75cd46d5..fbafdb0e446f 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities.py +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities.py @@ -3,7 +3,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ - +import os import pytest import platform import functools @@ -17,6 +17,7 @@ TextDocumentInput, VERSION, TextAnalyticsApiVersion, + PiiEntityDomainType, ) # pre-apply the client_cls positional argument so it needn't be explicitly passed below @@ -573,4 +574,35 @@ def test_recognize_pii_entities_v3(self, client): with pytest.raises(NotImplementedError) as excinfo: client.recognize_pii_entities(["this should fail"]) - assert "'recognize_pii_entities' endpoint is only available for API version v3.1-preview.1 and up" in str(excinfo.value) \ No newline at end of file + assert "'recognize_pii_entities' endpoint is only available for API version v3.1-preview.1 and up" in str(excinfo.value) + + # currently only have this as playback since the dev endpoint is unreliable + @pytest.mark.playback_test_only + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer(client_kwargs={ + "api_version": TextAnalyticsApiVersion.V3_1_PREVIEW_2, + "text_analytics_account_key": os.environ.get('AZURE_TEXT_ANALYTICS_KEY'), + "text_analytics_account": "https://cognitiveusw2dev.azure-api.net/" + }) + def test_redacted_text(self, client): + result = client.recognize_pii_entities(["My SSN is 859-98-0987."]) + self.assertEqual("My SSN is ***********.", result[0].redacted_text) + + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer() + def test_redacted_text_v3_1_preview_1(self, client): + result = client.recognize_pii_entities(["My SSN is 859-98-0987."]) + self.assertIsNone(result[0].redacted_text) + + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer() + def test_phi_domain_filter(self, client): + # without the domain filter, this should return two entities: Microsoft as an org, + # and the phone number. With the domain filter, it should only return one. + result = client.recognize_pii_entities( + ["I work at Microsoft and my phone number is 333-333-3333"], + domain_filter=PiiEntityDomainType.PROTECTED_HEALTH_INFORMATION + ) + self.assertEqual(len(result[0].entities), 1) + self.assertEqual(result[0].entities[0].text, '333-333-3333') + self.assertEqual(result[0].entities[0].category, 'Phone Number') diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities_async.py b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities_async.py index fb2d67bcdcb9..863504c2ebbc 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities_async.py +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/test_recognize_pii_entities_async.py @@ -3,7 +3,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ - +import os import pytest import platform import functools @@ -18,6 +18,7 @@ TextDocumentInput, VERSION, TextAnalyticsApiVersion, + PiiEntityDomainType, ) # pre-apply the client_cls positional argument so it needn't be explicitly passed below @@ -572,3 +573,34 @@ async def test_recognize_pii_entities_v3(self, client): await client.recognize_pii_entities(["this should fail"]) assert "'recognize_pii_entities' endpoint is only available for API version v3.1-preview.1 and up" in str(excinfo.value) + + # currently only have this as playback since the dev endpoint is unreliable + @pytest.mark.playback_test_only + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer(client_kwargs={ + "api_version": TextAnalyticsApiVersion.V3_1_PREVIEW_2, + "text_analytics_account_key": os.environ.get('AZURE_TEXT_ANALYTICS_KEY'), + "text_analytics_account": "https://cognitiveusw2dev.azure-api.net/" + }) + async def test_redacted_text(self, client): + result = await client.recognize_pii_entities(["My SSN is 859-98-0987."]) + self.assertEqual("My SSN is ***********.", result[0].redacted_text) + + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer() + async def test_redacted_text_v3_1_preview_1(self, client): + result = await client.recognize_pii_entities(["My SSN is 859-98-0987."]) + self.assertIsNone(result[0].redacted_text) + + @GlobalTextAnalyticsAccountPreparer() + @TextAnalyticsClientPreparer() + async def test_phi_domain_filter(self, client): + # without the domain filter, this should return two entities: Microsoft as an org, + # and the phone number. With the domain filter, it should only return one. + result = await client.recognize_pii_entities( + ["I work at Microsoft and my phone number is 333-333-3333"], + domain_filter=PiiEntityDomainType.PROTECTED_HEALTH_INFORMATION + ) + self.assertEqual(len(result[0].entities), 1) + self.assertEqual(result[0].entities[0].text, '333-333-3333') + self.assertEqual(result[0].entities[0].category, 'Phone Number') diff --git a/sdk/textanalytics/azure-ai-textanalytics/tests/test_repr.py b/sdk/textanalytics/azure-ai-textanalytics/tests/test_repr.py index eca9da158bff..602c910bdcd3 100644 --- a/sdk/textanalytics/azure-ai-textanalytics/tests/test_repr.py +++ b/sdk/textanalytics/azure-ai-textanalytics/tests/test_repr.py @@ -116,12 +116,15 @@ def linked_entity(linked_entity_match): language="English", data_source_entity_id="Bill Gates", url="https://en.wikipedia.org/wiki/Bill_Gates", - data_source="wikipedia" + data_source="wikipedia", + bing_entity_search_api_id="12345678" ) model_repr = ( "LinkedEntity(name=Bill Gates, matches=[{}, {}], "\ "language=English, data_source_entity_id=Bill Gates, "\ - "url=https://en.wikipedia.org/wiki/Bill_Gates, data_source=wikipedia)".format(linked_entity_match[1], linked_entity_match[1]) + "url=https://en.wikipedia.org/wiki/Bill_Gates, data_source=wikipedia, bing_entity_search_api_id=12345678)".format( + linked_entity_match[1], linked_entity_match[1] + ) ) assert repr(model) == model_repr return model, model_repr @@ -287,11 +290,13 @@ def test_recognize_pii_entities_result(self, pii_entity, text_analytics_warning, model = _models.RecognizePiiEntitiesResult( id="1", entities=[pii_entity[0]], + redacted_text="***********", warnings=[text_analytics_warning[0]], statistics=text_document_statistics[0], is_error=False ) - model_repr = "RecognizePiiEntitiesResult(id=1, entities=[{}], warnings=[{}], statistics={}, is_error=False)".format( + model_repr = "RecognizePiiEntitiesResult(id=1, entities=[{}], redacted_text=***********, warnings=[{}], " \ + "statistics={}, is_error=False)".format( pii_entity[1], text_analytics_warning[1], text_document_statistics[1] ) diff --git a/shared_requirements.txt b/shared_requirements.txt index 9ff3c816582d..e8075c11025b 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -10,6 +10,7 @@ azure-common~=1.1 azure-core<2.0.0,>=1.2.2 azure-cosmosdb-table~=1.0 azure-datalake-store~=0.0.18 +azure-data-nspkg<2.0.0,>=1.0.0 azure-eventhub<6.0.0,>=5.0.0 azure-eventgrid~=1.1 azure-graphrbac~=0.40.0