Skip to content

Commit e3e649d

Browse files
Merge pull request #16 from dragonchain/master
Release 4.2.0
2 parents 5102aec + be56b8d commit e3e649d

10 files changed

Lines changed: 1132 additions & 79 deletions

File tree

docs/changelog.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog
22
=========
33

4+
4.2.0
5+
-----
6+
7+
Features:
8+
* Add api key permissioning support
9+
Documentation:
10+
* Modify various client function docstrings to be more accurate
11+
Development:
12+
* Add integration tests for api key permissions
13+
* Modify run.sh to only act on code in dragonchain_sdk and tests
14+
415
4.1.0
516
-----
617

dragonchain_sdk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from dragonchain_sdk import dragonchain_client
1717

1818
__author__ = "Dragonchain, Inc."
19-
__version__ = "4.1.0"
19+
__version__ = "4.2.0"
2020

2121
ASYNC_SUPPORT = False
2222

dragonchain_sdk/dragonchain_client.py

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
logger = logging.getLogger(__name__)
2020

2121
if TYPE_CHECKING:
22-
from dragonchain_sdk.types import request_response, custom_index_fields_type # noqa: F401 used by typing
22+
from dragonchain_sdk.types import request_response, custom_index_fields_type, permissions_doc # noqa: F401 used by typing
2323

2424

2525
class Client(object):
@@ -549,40 +549,67 @@ def list_api_keys(self) -> "request_response":
549549
"""
550550
return self.request.get("/v1/api-key")
551551

552-
def create_api_key(self, nickname: Optional[str] = None) -> "request_response":
552+
def create_api_key(self, nickname: Optional[str] = None, permissions_document: Optional["permissions_doc"] = None) -> "request_response":
553553
"""Generate a new HMAC API key
554554
555+
Args:
556+
nickname (str, optional): The nickname to give to this new api key
557+
permissions_document (dict, optional): The permissions dictionary to use with this key.
558+
Check `the dragonchain core docs <https://dragonchain-core-docs.dragonchain.com/latest/usage/permissioning.html>`_ for more details
559+
555560
Returns:
556-
A new API key ID and key
561+
The newly created api key
557562
"""
558-
body = {}
563+
body = cast(Dict[str, Any], {})
559564
if nickname:
560565
if not isinstance(nickname, str):
561566
raise TypeError('Parameter "nickname" must be of type str.')
562-
body.update({"nickname": nickname})
567+
body["nickname"] = nickname
568+
if permissions_document:
569+
if not isinstance(permissions_document, dict):
570+
raise TypeError('Parameter "permissions_document" must be of type dict.')
571+
body["permissions_document"] = permissions_document
563572
return self.request.post("/v1/api-key", body)
564573

565574
def delete_api_key(self, key_id: str) -> "request_response":
566575
"""Delete an existing HMAC API key
567576
577+
Args:
578+
key_id (str): The id of the api key to delete
579+
568580
Returns:
569-
204 No Content on success
581+
Generic success message
570582
"""
571583
if not isinstance(key_id, str):
572584
raise TypeError('Parameter "key_id" must be of type str.')
573585
return self.request.delete("/v1/api-key/{}".format(key_id))
574586

575-
def update_api_key(self, key_id: str, nickname: str) -> "request_response":
587+
def update_api_key(
588+
self, key_id: str, nickname: Optional[str] = None, permissions_document: Optional["permissions_doc"] = None
589+
) -> "request_response":
576590
"""Update the nickname of an existing HMAC API key
577591
592+
Args:
593+
key_id (str): The id of the key to update
594+
nickname (str, optional): The nickname to set on this api key
595+
permissions_document (dict, optional): The permissions dictionary to set with this key.
596+
Check `the dragonchain core docs <https://dragonchain-core-docs.dragonchain.com/latest/usage/permissioning.html>`_ for more details
597+
578598
Returns:
579-
success: True
599+
The updated api key
580600
"""
581601
if not isinstance(key_id, str):
582602
raise TypeError('Parameter "key_id" must be of type str.')
583-
if not isinstance(nickname, str):
584-
raise TypeError('Parameter "nickname" must be of type str.')
585-
return self.request.put("/v1/api-key/{}".format(key_id), {"nickname": nickname})
603+
body = cast(Dict[str, Any], {})
604+
if nickname:
605+
if not isinstance(nickname, str):
606+
raise TypeError('Parameter "nickname" must be of type str.')
607+
body["nickname"] = nickname
608+
if permissions_document:
609+
if not isinstance(permissions_document, dict):
610+
raise TypeError('Parameter "permissions_document" must be of type dict.')
611+
body["permissions_document"] = permissions_document
612+
return self.request.put("/v1/api-key/{}".format(key_id), body)
586613

587614
def get_smart_contract_object(self, key: str, smart_contract_id: Optional[str] = None) -> "request_response":
588615
"""Retrieve data from the object storage of a smart contract
@@ -663,23 +690,25 @@ def create_transaction_type(
663690
) -> "request_response":
664691
"""Creates a new custom transaction type.
665692
666-
Transaction Types can optionally link custom search index fields to your transactions for easier querying later.
667-
A custom_index_field is a dictionary with 'path', 'field_name', 'type', and an optional 'options' dictionary:
668-
669-
path (str): the JSONPath of your transaction payload you would like to result form a search on the "key".
670-
field_name (str): The field for this custom extracted value to be indexed under
671-
type ('text', 'tag', or 'number'): The type of redisearch index to use for this field
672-
options: (object) The redisearch options for this field
673-
- no_index (bool) (all types) whether or not to index on this field, or simply make it sortable only if false
674-
- separator (str) (tag only) what string should be used for the tag separator
675-
- weight (number) (text only) The weight to give this text field when doing text queries
676-
- no_stem (bool) (text only) Whether or not to allow search stemming in text searches on this field
677-
- sortable (bool) (text and number only) Whether or not a search on the index can be sortable by this field
678-
See redisearch for more details on these options: https://oss.redislabs.com/redisearch/Commands.html#field_options
679-
680693
Args:
681694
transaction_type (str): transaction_type to update
682-
custom_index_fields (optional): custom_index_fields to update. Ex.: [{"path":"a.b","field_name":"myField","type":"text","options":{"no_index":True}}]
695+
custom_index_fields (optional): custom index fields to create. Ex.: ``[{"path":"a.b","field_name":"myField","type":"text","options":{"no_index":True}}]``
696+
697+
Transaction Types can optionally link custom search index fields to your transactions for easier querying later.
698+
A custom_index_field is a dictionary with 'path', 'field_name', 'type', and an optional 'options' dictionary:
699+
700+
- path (str): the JSONPath of your transaction payload you would like to result form a search on the "key"
701+
- field_name (str): The field for this custom extracted value to be indexed under
702+
- type ('text', 'tag', or 'number'): The type of redisearch index to use for this field
703+
- options: (object) The redisearch options for this field
704+
705+
- no_index (bool) (all types) whether or not to index on this field, or simply make it sortable only if false
706+
- separator (str) (tag only) what string should be used for the tag separator
707+
- weight (number) (text only) The weight to give this text field when doing text queries
708+
- no_stem (bool) (text only) Whether or not to allow search stemming in text searches on this field
709+
- sortable (bool) (text and number only) Whether or not a search on the index can be sortable by this field
710+
711+
See `the redisearch docs <https://oss.redislabs.com/redisearch/Commands.html#field_options>`_ for more details on these options
683712
684713
Raises:
685714
TypeError: with bad parameter types
@@ -691,8 +720,8 @@ def create_transaction_type(
691720
custom_index_fields = []
692721
if not isinstance(transaction_type, str):
693722
raise TypeError('Parameter "transaction_type" must be of type str.')
694-
if not isinstance(custom_index_fields, list):
695-
raise TypeError('Parameter "custom_index_fields" must be of type list.')
723+
if not isinstance(custom_index_fields, (list, set, tuple)):
724+
raise TypeError('Parameter "custom_index_fields" must be iterable.')
696725
params = cast(Dict[str, Any], {"version": "2", "txn_type": transaction_type})
697726
if custom_index_fields:
698727
params["custom_indexes"] = _validate_and_build_custom_index_fields_array(custom_index_fields)

dragonchain_sdk/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
custom_index_fields_type = mypy_extensions.TypedDict(
2121
"custom_index_fields_type", {"path": str, "field_name": str, "type": str, "options": Dict[str, Any]}
2222
)
23+
permissions_doc = mypy_extensions.TypedDict("permissions_doc", {"version": str, "default_allow": bool, "permissions": Dict[str, Any]})

run.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,16 @@ elif [ "$1" = "tests" ]; then
4848
fi
4949
elif [ "$1" = "lint" ]; then
5050
if [ "$2" = "no-async" ]; then
51-
find . -name "*.py" -not -name "*async*" -exec python3 -m flake8 {} +
51+
find dragonchain_sdk -name "*.py" -not -name "*async*" -exec python3 -m flake8 {} +
52+
find tests -name "*.py" -not -name "*async*" -exec python3 -m flake8 {} +
5253
else
53-
find . -name "*.py" -exec python3 -m flake8 {} +
54-
if [ "$2" != "no-format" ]; then python3 -m black --check -l 150 -t py34 .; fi
54+
find dragonchain_sdk -name "*.py" -exec python3 -m flake8 {} +
55+
find tests -name "*.py" -exec python3 -m flake8 {} +
56+
if [ "$2" != "no-format" ]; then python3 -m black --check -l 150 -t py34 dragonchain_sdk; python3 -m black --check -l 150 -t py34 tests; fi
5557
fi
5658
elif [ "$1" = "format" ]; then
57-
python3 -m black -l 150 -t py34 .
59+
python3 -m black -l 150 -t py34 dragonchain_sdk
60+
python3 -m black -l 150 -t py34 tests
5861
elif [ "$1" = "bandit" ]; then
5962
python3 -m bandit -r dragonchain_sdk/
6063
elif [ "$1" = "build" ]; then

tests/integration/run.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,19 @@
1919
from tests.integration import test_api_keys
2020
from tests.integration import test_misc
2121
from tests.integration import test_interchain
22+
from tests.integration import test_permissioning
2223

2324
if __name__ == "__main__":
24-
test_suites = [test_transaction_types, test_transactions, test_smart_contracts, test_blocks, test_api_keys, test_misc, test_interchain]
25+
test_suites = [
26+
test_transaction_types,
27+
test_transactions,
28+
test_smart_contracts,
29+
test_blocks,
30+
test_api_keys,
31+
test_misc,
32+
test_interchain,
33+
test_permissioning,
34+
]
2535
# Make one suite and add all our tests
2636
suite = unittest.TestSuite()
2737
for test in test_suites:

tests/integration/schema.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,43 @@
7272

7373
create_transaction_response_schema = {"type": "object", "properties": {"transaction_id": {"type": "string"}}}
7474

75+
permissions_document_schema = {
76+
"type": "object",
77+
"properties": {"version": {"type": "string", "enum": ["1"]}, "default_allow": {"type": "boolean"}, "permissions": {"type": "object"}},
78+
"required": ["version", "default_allow", "permissions"],
79+
"additionalProperties": False,
80+
}
81+
7582
api_key_create_schema = {
7683
"type": "object",
77-
"properties": {"key": {"type": "string"}, "id": {"type": "string"}, "registration_time": {"type": "integer"}, "nickname": {"type": "string"}},
78-
"required": ["key", "id", "registration_time"],
84+
"properties": {
85+
"key": {"type": "string"},
86+
"id": {"type": "string"},
87+
"registration_time": {"type": "integer"},
88+
"nickname": {"type": "string"},
89+
"root": {"type": "boolean"},
90+
"permissions_document": permissions_document_schema,
91+
},
92+
"required": ["key", "id", "registration_time", "nickname", "root", "permissions_document"],
7993
"additionalProperties": False,
8094
}
8195

82-
api_key_list_schema = {
96+
api_key_generic_schema = {
8397
"type": "object",
8498
"properties": {
85-
"keys": {
86-
"type": "array",
87-
"items": {
88-
"type": "object",
89-
"properties": {
90-
"id": {"type": "string"},
91-
"registration_time": {"type": "integer"},
92-
"nickname": {"type": "string"},
93-
"root": {"type": "boolean"},
94-
},
95-
"required": ["id", "registration_time"],
96-
"additionalProperties": False,
97-
},
98-
}
99+
"id": {"type": "string"},
100+
"registration_time": {"type": "integer"},
101+
"nickname": {"type": "string"},
102+
"root": {"type": "boolean"},
103+
"permissions_document": permissions_document_schema,
99104
},
105+
"required": ["id", "registration_time", "nickname", "root", "permissions_document"],
106+
"additionalProperties": False,
107+
}
108+
109+
api_key_list_schema = {
110+
"type": "object",
111+
"properties": {"keys": {"type": "array", "items": api_key_generic_schema}},
100112
"required": ["keys"],
101113
"additionalProperties": False,
102114
}

0 commit comments

Comments
 (0)