Skip to content

Commit

Permalink
Merge pull request Azure#8 from seankane-msft/clarify-readme
Browse files Browse the repository at this point in the history
Fixes Bad Request to more clear error
  • Loading branch information
seankane-msft authored Jul 17, 2020
2 parents a60c5cd + adb71c2 commit 12a5df3
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 19 deletions.
48 changes: 29 additions & 19 deletions sdk/table/azure-table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ or [Azure CLI](https://docs.microsoft.com/azure/storage/common/storage-quickstar
```bash
# Create a new resource group to hold the storage account -
# if using an existing resource group, skip this step
az group create --name my-resource-group --location westus2
az group create --name MyResourceGroup --location westus2

# Create the storage account
az storage account create -n my-storage-account-name -g my-resource-group
az storage account create -n mystorageaccount -g MyResourceGroup
```

### Create the client
Expand All @@ -48,7 +48,7 @@ you to access the storage account:
```python
from azure.table import TableServiceClient

service = TableServiceClient(account_url="https://<my-storage-account-name>.table.core.windows.net/", credential=credential)
service = TableServiceClient(account_url="https://<mystorageaccount>.table.core.windows.net/", credential=credential)
```

#### Looking up the account URL
Expand All @@ -59,7 +59,7 @@ or [Azure CLI](https://docs.microsoft.com/cli/azure/storage/account?view=azure-c

```bash
# Get the table service URL for the storage account
az storage account show -n my-storage-account-name -g my-resource-group --query "primaryEndpoints.table"
az storage account show -n mystorageaccount -g MyResourceGroup --query "primaryEndpoints.table"
```

#### Types of credentials
Expand Down Expand Up @@ -89,7 +89,7 @@ The `credential` parameter may be provided in a number of different forms, depen
(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```
```az storage account keys list -g MyResourceGroup -n mystorageaccount```

Use the key as the credential parameter to authenticate the client:
```python
Expand All @@ -112,7 +112,7 @@ service = TableServiceClient.from_connection_string(conn_str=connection_string)
The connection string to your storage account can be found in the Azure Portal under the "Access Keys" section or by running the following CLI command:

```bash
az storage account show-connection-string -g MyResourceGroup -n MyStorageAccount
az storage account show-connection-string -g MyResourceGroup -n mystorageaccount
```

## Key concepts
Expand Down Expand Up @@ -158,16 +158,16 @@ Create a table in your storage account
```python
from azure.table import TableServiceClient

table = TableServiceClient.from_connection_string(conn_str="<connection_string>")
table.create_table(table_name="myTable")
table_service_client = TableServiceClient.from_connection_string(conn_str="<connection_string>")
table_service_client.create_table(table_name="myTable")
```

Use the async client to create a table
```python
from azure.table.aio import TableServiceClient

table = TableServiceClient.from_connection_string(conn_str="<connection_string>")
await table.create_table(table_name="myTable")
table_service_client = TableServiceClient.from_connection_string(conn_str="<connection_string>")
await table_service_client.create_table(table_name="myTable")
```

### Creating entities
Expand All @@ -178,19 +178,25 @@ from azure.table import TableClient

my_entity = {'PartitionKey':'part','RowKey':'row'}

table = TableClient.from_connection_string(conn_str="<connection_string>", table_name="my_table")
entity = table.create_entity(table_entity_properties=my_entity)
table_client = TableClient.from_connection_string(conn_str="<connection_string>", table_name="myTable")
entity = table_client.create_entity(entity=my_entity)
```

Create entities asynchronously

```python
from azure.table.aio import TableClient

my_entity = {'PartitionKey':'part','RowKey':'row'}
my_entity = {
'PartitionKey': 'color',
'RowKey': 'brand',
'text': 'Marker',
'color': 'Purple',
'price': '5',
}

table = TableClient.from_connection_string(conn_str="<connection_string>", table_name="my_table")
entity = await table.create_entity(table_entity_properties=my_entity)
table_client = TableClient.from_connection_string(conn_str="<connection_string>", table_name="mytable")
entity = await table_client.create_entity(entity=my_entity)
```

### Querying entities
Expand All @@ -199,17 +205,21 @@ Querying entities in the table
```python
from azure.table import TableClient

table = TableClient.from_connection_string(conn_str="<connection_string>", table_name="my_table")
entity = table.query_entities(results_per_page=3)
my_filter = "text eq Marker"

table_client = TableClient.from_connection_string(conn_str="<connection_string>", table_name="mytable")
entity = table_client.query_entities(filter=my_filter)
```

Querying entities asynchronously

```python
from azure.table.aio import TableClient

table = TableClient.from_connection_string(conn_str="<connection_string>", table_name="my_table")
entity = await table.query_entities(results_per_page=3)
my_filter = "text eq Marker"

table_client = TableClient.from_connection_string(conn_str="<connection_string>", table_name="mytable")
entity = await table_client.query_entities(filter=my_filter)
```

## Optional Configuration
Expand Down
8 changes: 8 additions & 0 deletions sdk/table/azure-table/azure/table/_shared/_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# license information.
# --------------------------------------------------------------------------
from sys import version_info
from re import match

from azure.core.exceptions import HttpResponseError, ResourceExistsError, ResourceNotFoundError
from azure.table._shared.parser import _str
Expand Down Expand Up @@ -214,6 +215,13 @@ def _wrap_exception(ex, desired_type):
return desired_type('{}: {}'.format(ex.__class__.__name__, msg))


def _validate_table_name(table_name):
if match("^[a-zA-Z]{1}[a-zA-Z0-9]{2,62}$", table_name) is None:
raise ValueError(
"Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long."
)


class AzureSigningError(Exception):
"""
Represents a fatal error when attempting to sign a request.
Expand Down
4 changes: 4 additions & 0 deletions sdk/table/azure-table/azure/table/_table_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# --------------------------------------------------------------------------

import functools
import re
from typing import Optional, Any

try:
Expand All @@ -20,6 +21,7 @@
from azure.table._generated.models import AccessPolicy, SignedIdentifier, TableProperties, QueryOptions
from azure.table._serialize import _get_match_headers, _add_entity_properties
from azure.table._shared.base_client import StorageAccountHostsMixin, parse_query, parse_connection_str
from azure.table._shared._error import _validate_table_name

from azure.table._shared.request_handlers import serialize_iso
from azure.table._shared.response_handlers import process_table_error
Expand Down Expand Up @@ -56,6 +58,8 @@ def __init__(
:returns: None
"""

_validate_table_name(table_name)

try:
if not account_url.lower().startswith('http'):
account_url = "https://" + account_url
Expand Down
6 changes: 6 additions & 0 deletions sdk/table/azure-table/azure/table/_table_service_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# --------------------------------------------------------------------------

import functools
import re
from typing import Any

from azure.core.pipeline import Pipeline
Expand All @@ -26,6 +27,7 @@
from azure.core.paging import ItemPaged
from azure.core.tracing.decorator import distributed_trace
from azure.table._table_client import TableClient
from azure.table._shared._error import _validate_table_name


class TableServiceClient(StorageAccountHostsMixin):
Expand Down Expand Up @@ -177,6 +179,8 @@ def create_table(
:rtype: ~azure.table.TableClient
:raises: ~azure.core.exceptions.HttpResponseError
"""
_validate_table_name(table_name)

table_properties = TableProperties(table_name=table_name, **kwargs)
self._client.table.create(table_properties)
table = self.get_table_client(table_name=table_name)
Expand All @@ -196,6 +200,8 @@ def delete_table(
:return: None
:rtype: None
"""
_validate_table_name(table_name)

self._client.table.delete(table=table_name, **kwargs)

@distributed_trace
Expand Down
1 change: 1 addition & 0 deletions sdk/table/azure-table/dev_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-e ../../../tools/azure-devtools
-e ../../../tools/azure-sdk-tools
-e ../../identity/azure-identity
../../core/azure-core
cryptography>=2.1.4
aiohttp>=3.0; python_version >= '3.5'
Expand Down
22 changes: 22 additions & 0 deletions sdk/table/azure-table/tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ def test_create_table_fail_on_exist(self, resource_group, location, storage_acco
# self.assertEqual(existing[0], [table_name])
ts.delete_table(table_name)

@GlobalStorageAccountPreparer()
def test_create_table_invalid_name(self, resource_group, location, storage_account, storage_account_key):
# Arrange
ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key)
invalid_table_name = "my_table"

with pytest.raises(ValueError) as excinfo:
ts.create_table(invalid_table_name)

assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str(excinfo)

@GlobalStorageAccountPreparer()
def test_delete_table_invalid_name(self, resource_group, location, storage_account, storage_account_key):
# Arrange
ts = TableServiceClient(self.account_url(storage_account, "table"), storage_account_key)
invalid_table_name = "my_table"

with pytest.raises(ValueError) as excinfo:
ts.create_table(invalid_table_name)

assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str(excinfo)

# @pytest.mark.skip("pending")
@GlobalStorageAccountPreparer()
def test_query_tables(self, resource_group, location, storage_account, storage_account_key):
Expand Down
17 changes: 17 additions & 0 deletions sdk/table/azure-table/tests/test_table_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# )
from _shared.testcase import GlobalStorageAccountPreparer, TableTestCase

from azure.core.exceptions import HttpResponseError
# ------------------------------------------------------------------------------
SERVICES = {
#TableServiceClient: 'table',
Expand Down Expand Up @@ -543,6 +544,22 @@ def test_create_table_client_with_complete_url(self, resource_group, location, s
self.assertEqual(service.table_name, 'bar')
self.assertEqual(service.account_name, storage_account.name)

# @pytest.mark.skip("pending")
@GlobalStorageAccountPreparer()
def test_create_table_client_with_invalid_name(self, resource_group, location, storage_account, storage_account_key):
# Arrange
table_url = "https://{}.table.core.windows.net:443/foo".format(storage_account.name)
invalid_table_name = "my_table"

# Assert
with pytest.raises(ValueError) as excinfo:
service = TableClient(account_url=table_url, table_name=invalid_table_name, credential=storage_account_key)

assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str(excinfo)

# with self.assertRaises(ValueError):
# service = TableClient(account_url=table_url, table_name=invalid_table_name, credential=storage_account_key)

#@pytest.mark.skip("pending")
def test_error_with_malformed_conn_str(self):
# Arrange
Expand Down

0 comments on commit 12a5df3

Please sign in to comment.