├── .gitignore ├── CODE_INFO.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── nemnis ├── __init__.py ├── asyncio.py ├── client.py └── core.py ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── test_account.py ├── test_blockChain.py ├── test_client.py ├── test_namespace.py ├── test_node.py └── test_transaction.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # PyCharm 104 | .idea 105 | -------------------------------------------------------------------------------- /CODE_INFO.md: -------------------------------------------------------------------------------- 1 | 2 | ## nis-python-client code guide 3 | #### main module: nemnis 4 | 5 | ---------- 6 | 7 | ### Requirements: 8 | 9 | [requests](http://python-requests.org/en/master/) 10 | [requests-mock](http://requests-mock.readthedocs.io/en/latest/) 11 | 12 | ---------- 13 | 14 | 15 | ### Classes: 16 | 17 | - #### [Account](#class-account) 18 | 19 | - #### [BlockChain](#class-blockchain) 20 | 21 | - #### [Client](#class-client) 22 | 23 | - #### [Debug](#class-debug) 24 | 25 | - #### [Namespace](#class-namespace) 26 | 27 | - #### [Node](#class-node) 28 | 29 | - #### [Transaction](#class-transaction) 30 | 31 | ---------- 32 | 33 | ### class **Account** 34 | 35 | _Implements [account](https://nemproject.github.io/#account-related-requests) related methods from API._ 36 | 37 | 38 | #### Methods: 39 | 40 | - #### **\_\_init\_\_**(self, client) 41 | Initialize self. See `help(type(self))` for accurate signature. 42 | 43 | - **client**: Client instance 44 | 45 | 46 | - #### **generate**(self) 47 | Implements [generating-new-account-data](https://nemproject.github.io/#generating-new-account-data). 48 | Generates a [**KeyPairViewModel**](https://nemproject.github.io/#keyPairViewModel). 49 | 50 | - #### **get**(self, address) 51 | Implements [requesting-the-account-data](https://nemproject.github.io/#requesting-the-account-data). 52 | Gets an [**AccountMetaDataPair**](https://nemproject.github.io/#accountMetaDataPair) for an account. 53 | If the address parameter is not valid, NIS returns an error. 54 | 55 | - **address**: the address of the account. 56 | 57 | - #### **get_forwarded**(self, address) 58 | Implements [requesting-the-original-account-data-for-a-delegate-account](https://nemproject.github.io/#requesting-the-original-account-data-for-a-delegate-account). 59 | Given a delegate (formerly known as remote) account's address, 60 | gets the [**AccountMetaDataPair**](https://nemproject.github.io/#accountMetaDataPair) for the account for which the given account is the delegate account. If the given account address is not a delegate 61 | account for any account, the request returns the [**AccountMetaDataPair**](https://nemproject.github.io/#accountMetaDataPair) for the given address. 62 | If the address parameter is not valid, NIS returns an error. 63 | 64 | - **address**: the address of the delegate account. 65 | 66 | - #### **get\_forwarded\_from\_public\_key**(self, pub_key) 67 | 68 | Implements [requesting-the-original-account-data-for-a-delegate-account](https://nemproject.github.io/#requesting-the-original-account-data-for-a-delegate-account). 69 | Alternative to ***get_forwarded*** method. You can retrieve the original 70 | account data by providing the public key of the delegate account. 71 | If the public key parameter is not valid, NIS returns an error. 72 | 73 | - **pub_key**: the public key of the account as hex string. 74 | 75 | - #### **get\_from\_public\_key**(self, pub_key) 76 | 77 | Implements [requesting-the-account-data](https://nemproject.github.io/#requesting-the-account-data). 78 | Alternative to ***get*** method. You can retrieve the account data 79 | by providing the public key for the account. 80 | If the public key parameter is not valid, NIS returns an error 81 | 82 | - **pub_key**: The public key of the account as hex string. 83 | 84 | - #### **harvests**(self, address, _hash) 85 | 86 | Implements [requesting-harvest-info-data-for-an-account](https://nemproject.github.io/#requesting-harvest-info-data-for-an-account). 87 | Gets an array of harvest info objects for an account. 88 | If the address parameter is not valid or the hash cannot be found in 89 | the database, NIS returns an error. 90 | 91 | - **address**: the address of the account. 92 | - **\_hash**: the 256 bit sha3 hash of the block up to which harvested blocks are returned. 93 | 94 | - #### **historical_get**(self, address, start\_height, end\_height, inc) 95 | 96 | Implements [retrieving-historical-account-data](https://nemproject.github.io/#retrieving-historical-account-data). 97 | Gets historical information for an account. 98 | To turn on this feature for your NIS, you need to add 99 | ***HISTORICAL\_ACCOUNT\_DATA*** to the list of optional features in the file 100 | ***config.properties***. 101 | If the address is invalid, the ***start_height*** is larger than the 102 | ***end_height***, the increment is not a positive or the request results in 103 | more than 1000 data points an error is returned. 104 | 105 | - **address**: the address of the account. 106 | 107 | - **start\_height**: the block height from which on the data should be supplied. 108 | 109 | - **end\_height**: the block height up to which the data should be supplied. The end height must be greater than or equal to the start height. 110 | 111 | - **inc**: The value by which the height is incremented between each data point. The value must be greater than _0_. NIS can supply up to _1000_ data points with one request. Requesting more than _1000_ data points results in an error. 112 | 113 | - #### **importances**(self) 114 | 115 | Implements [retrieving-account-importances-for-accounts](https://nemproject.github.io/#retrieving-account-importances-for-accounts). 116 | Gets an array of account importance view model objects. 117 | 118 | - #### **lock**(self, private_key) 119 | 120 | Implements [locking-and-unlocking-accounts](https://nemproject.github.io/#locking-and-unlocking-accounts) 121 | Locks an account (stops harvesting). 122 | Request return an error if the private key does not correspond to a 123 | known account or the account is not allowed to harvest. 124 | 125 | - **private_key**: A [**PrivateKey**](https://nemproject.github.io/#privateKey) JSON object 126 | 127 | 128 | - #### **mosaic\_definition\_page**(self, address, parent, _id) 129 | 130 | Implements [retrieving-mosaic-definitions-that-an-account-has-created](https://nemproject.github.io/#retrieving-mosaic-definitions-that-an-account-has-created). 131 | Gets an array of mosaic definition objects for a given account address. 132 | If ***parent*** param is supplied, only mosaic definitions for the given 133 | parent namespace are returned. 134 | NIS returns an error if the address, the parent (if supplied) 135 | or the id (if supplied) is invalid. 136 | 137 | - **address**: the address of the account. 138 | - **parent**: _(optional)_ parent namespace id. 139 | - **\_id**: _(optional)_ mosaic definition database id up to which mosaic 140 | definitions are returned. 141 | 142 | - #### **mosaic_owned**(self, address) 143 | 144 | Implements [retrieving-mosaics-that-an-account-owns](https://nemproject.github.io/#retrieving-mosaics-that-an-account-owns). 145 | Gets an array of mosaic objects for a given account address. 146 | NIS returns an error if the address is invalid. 147 | 148 | - **address**: the address of the account. 149 | 150 | - #### **namespace_page**(self, address, parent, \_id, page\_size=None) 151 | 152 | Implements [retrieving-namespaces-that-an-account-owns](https://nemproject.github.io/#retrieving-namespaces-that-an-account-owns). 153 | Gets an array of namespace objects for a given account address. 154 | If **parent** param is supplied, only sub-namespaces of the parent 155 | namespace are returned. 156 | NIS returns an error if the address or the parent (if supplied) 157 | is invalid. 158 | 159 | - **address**: the address of the account. 160 | - **parent**: _(optional)_ parent namespace id. 161 | - **\_id**: _(optional)_ namespace database id up to which namespaces are returned. 162 | - **page_size:** _(optional)_ number of namespaces to be returned. 163 | 164 | - #### **status**(self, address) 165 | 166 | Implements [requesting-the-account-status](https://nemproject.github.io/#requesting-the-account-status). 167 | Gets the \`AccountMetaData\` from an account 168 | (https://nemproject.github.io/#accountMetaData). 169 | If the address parameter is not valid, NIS returns an error. 170 | 171 | - **address**: the address of the account. 172 | 173 | - #### **transfers_all**(self, address, \_hash, \_id=None) 174 | 175 | Implements [requesting-transaction-data-for-an-account](https://nemproject.github.io/#requesting-transaction-data-for-an-account) 176 | Gets an array of transaction meta data pairs for which an account is 177 | the sender or receiver. A maximum of _25_ transaction meta data pairs 178 | is returned. 179 | If the address parameter is not valid or the id cannot be found in the 180 | database, NIS returns an error. 181 | 182 | - **address**: the address of the account. 183 | - **\_hash**: _(optional)_ the 256 bit sha3 hash of the transaction up to which transactions are returned. 184 | - **\_id**: _(optional)_ the transaction id up to which transactions are 185 | returned. 186 | 187 | - #### **transfers_incoming**(self, address, \_hash=None, \_id=None) 188 | 189 | Implements [requesting-transaction-data-for-an-account ](https://nemproject.github.io/#requesting-transaction-data-for-an-account). 190 | Gets an array of [**TransactionMetaDataPair**](https://nemproject.github.io/#transactionMetaDataPair) objects. 191 | Has the address given as parameter to the request. A maximum of _25_ 192 | transaction meta data pairs is returned. The returned transaction meta 193 | data pairs are sorted in descending order in which they were written 194 | to the database. 195 | If the address parameter is not valid or the id cannot be found in the 196 | database, NIS returns an error. 197 | 198 | - **address**: the address of the account. 199 | - **\_hash**: _(optional)_ the 256 bit sha3 hash of the transaction up to 200 | which transactions are returned. 201 | - **\_id**: _(optional)_ the transaction id up to which transactions are 202 | returned. 203 | 204 | - #### **transfers_outgoing**(self, address, \_hash=None, \_id=None) 205 | 206 | Implements [requesting-transaction-data-for-an-account](https://nemproject.github.io/#requesting-transaction-data-for-an-account). 207 | Gets an array of transaction meta data pairs where the recipient has 208 | the address given as parameter to the request. 209 | A maximum of _25_ transaction meta data pairs is returned. 210 | f the address parameter is not valid or the id cannot be found in the 211 | database, NIS returns an error. 212 | 213 | - **address**: the address of the account. 214 | - **\_hash**: _(optional)_ the 256 bit sha3 hash of the transaction up to 215 | which transactions are returned. 216 | - **\_id**: _(optional)_ the transaction id up to which transactions are 217 | returned. 218 | 219 | - #### **unconfirmed_transactions**(self, address) 220 | 221 | Implements [requesting-transaction-data-for-an-account](https://nemproject.github.io/#requesting-transaction-data-for-an-account). 222 | Gets the array of transactions for which an account is the sender or 223 | receiver and which have not yet been included in a block. The returned 224 | structure is [**UnconfirmedTransactionMetaDataPair**](https://nemproject.github.io/#unconfirmedTransactionMetaDataPair). 225 | If the address parameter is not valid, NIS returns an error. 226 | 227 | - **address**: the address of the account. 228 | 229 | - #### **unlock**(self, private_key) 230 | 231 | Implements [locking-and-unlocking-accounts](https://nemproject.github.io/#locking-and-unlocking-accounts) 232 | Unlocks an account (starts harvesting). 233 | Request return an error if the private key does not correspond to a 234 | known account or the account is not allowed to harvest. 235 | 236 | - **private_key**: A [**PrivateKey**](https://nemproject.github.io/#privateKey) JSON object. 237 | 238 | 239 | - #### **unlocked_info**(self) 240 | 241 | Implements [retrieving-the-unlock-info](https://nemproject.github.io/#retrieving-the-unlock-info). 242 | Gives information about the maximum number of allowed harvesters and 243 | how many harvesters are already using the node. 244 | 245 | 246 | ### class **BlockChain** 247 | 248 | _Implements [block chain](https://nemproject.github.io/#block-chain-related-requests ) related methods from API._ 249 | 250 | #### Methods: 251 | 252 | - #### **\_\_init\_\_**(self, client) 253 | 254 | Initialize self. See `help(type(self))` for accurate signature. 255 | 256 | - **client**: Client instance 257 | 258 | - #### **at_public**(self, block_height) 259 | 260 | Gets a block from the chain that has the given height. 261 | If the block with the specified height cannot be found in the database, 262 | NIS will return a JSON error object. 263 | 264 | - **block_height**: A [**BlockHeight**](https://nemproject.github.io/#blockHeight) JSON object. 265 | 266 | - #### **height**(self) 267 | 268 | Gets the current height of the block chain. 269 | 270 | - #### **last_block**(self) 271 | 272 | Gets the current last block of the chain. 273 | 274 | - #### **local\_chain\_blocks_after**(self, block_height) 275 | 276 | Gets up to _10_ blocks after given block height from the chain. 277 | The returned data is an array of [**ExplorerBlockViewModel**](https://nemproject.github.io/#explorerBlockViewModel) JSON objects. 278 | If the block height supplied is not positive, NIS will return a 279 | JSON error object. 280 | 281 | - **block_height**: A [**BlockHeight**](https://nemproject.github.io/#blockHeight) JSON object. 282 | 283 | - #### **score**(self) 284 | 285 | Gets the current score of the block chain. 286 | 287 | 288 | ### class **Client** 289 | 290 | _Class that represents main API client.Make calls to NIS via related methods. 291 | For all required information, please follow:_ 292 | [here](https://nemproject.github.io/). 293 | _All available methods documentation is also can be found there._ 294 | 295 | 296 | #### Methods: 297 | 298 | - #### **\_\_init\_\_**(self, endpoint='http://127.0.0.1:7890') 299 | 300 | Initialize client. 301 | 302 | - **endpoint**: address of the NIS. 303 | 304 | - #### **call**(self, method, name, params=None, payload=None, \**kwargs) 305 | 306 | Make calls to the API via HTTP methods and passed params. 307 | Methods that uses this method returns response object. 308 | 309 | - **method**: HTTP method of the request ('GET', 'POST'). 310 | - **name**: name of the API endpoint method. Appends to base URL. 311 | - **params**: GET method params, used when method is GET. 312 | - **payload**: POST method data, used when method is POST. 313 | - **kwargs**: any additional arguments. 314 | - ***return***: [response](http://docs.python-requests.org/en/master/api/#requests.Response) object. 315 | 316 | - #### **heartbeat**(self) 317 | 318 | Implements [heart-beat-request](https://nemproject.github.io/#heart-beat-request). 319 | Determines if NIS is up and responsive. 320 | If there is no response to this request, NIS is either not running or 321 | is in a state where it can't serve requests. 322 | 323 | - #### **status**(self) 324 | 325 | Implements [status-request](https://nemproject.github.io/#status-request) 326 | Determines the status of NIS. 327 | If there is no response to this request, NIS is either not running or 328 | is in a state where it can't serve requests 329 | 330 | 331 | #### Properties: 332 | 333 | - #### **account** 334 | 335 | Represents account related requests from API. 336 | 337 | - ***return***: Account instance for use with it's methods. 338 | 339 | - #### **blockchain** 340 | 341 | Represents block chain related requests from API. 342 | 343 | - ***return***: BlockChain instance for use with it's methods. 344 | 345 | - #### **debug** 346 | 347 | Represents requests for additional information from NIS. 348 | 349 | - ***return***: Debug instance for use with its methods. 350 | 351 | - #### **namespace** 352 | 353 | Represents namespaces related requests from API. 354 | 355 | - ***return***: Namespace instance for use with its methods. 356 | 357 | - #### **node** 358 | 359 | Represents node related requests from API. 360 | 361 | - ***return***: Node instance for use with its methods. 362 | 363 | - #### **transaction** 364 | 365 | Represents transaction related requests methods from API. 366 | 367 | - ***return***: Transaction instance for use with its methods. 368 | 369 | 370 | ### class **Debug** 371 | 372 | _Implements requests for [additional information](https://nemproject.github.io/#requests-for-additional-information-from-NIS) from NIS._ 373 | 374 | 375 | #### Methods: 376 | 377 | - **\_\_init\_\_**(self, client) 378 | 379 | Initialize self. See `help(type(self))` for accurate signature. 380 | 381 | - **client**: Client instance 382 | 383 | - **connections_incoming**(self) 384 | 385 | Gets an audit collection of incoming calls. 386 | You can monitor the outstanding and recent incoming requests with 387 | this information. 388 | 389 | - **connections_outgoing**(self) 390 | 391 | Gets an audit collection of outgoing calls. 392 | You can monitor the outstanding and recent outgoing requests with 393 | this information. 394 | 395 | - **time_synchronization**(self) 396 | 397 | Gets an array of time synchronization results. 398 | You can monitor the change in network time with this information. 399 | 400 | 401 | - **timers**(self) 402 | 403 | Gets an array of task monitor structures. 404 | You can monitor the statistics for periodic tasks with 405 | this information. 406 | 407 | 408 | ### class **Namespace** 409 | 410 | _Implements [namespace related](https://nemproject.github.io/#namespaces-and-mosaics ) methods from API._ 411 | 412 | #### Methods: 413 | 414 | - **\_\_init\_\_**(self, client) 415 | 416 | Initialize self. See `help(type(self))` for accurate signature. 417 | 418 | - **client**: Client instance 419 | 420 | - **mosaic\_definition\_page**(self, namespace, \_id=None, pagesize=25) 421 | 422 | Gets the mosaic definitions for a given namespace. 423 | 424 | - **namespace**: the namespace id. 425 | - **\_id**: _(optional)_ the topmost mosaic definition database id up to 426 | which root mosaic definitions are returned. 427 | The parameter is optional. If not supplied the most recent 428 | mosaic definitiona are returned. 429 | - **pagesize**: the number of mosaic definition objects to be returned 430 | for each request. The parameter is optional. The default value 431 | is _25_, the minimum value is _5_ and hte maximum value is _100_. 432 | 433 | - **namespace**(self, namespace) 434 | 435 | Gets the namespace with given id. 436 | 437 | - **namespace**: the namespace id. 438 | 439 | - **root_page**(self, \_id=None, page\_size=25) 440 | 441 | Gets the root namespaces. 442 | 443 | - **\_id**: _(optional)_ the topmost namespace database id up to which 444 | root namespaces are returned 445 | - **page_size**: _(optional )_the number of namespace objects to be 446 | returned for each request. The parameter is optional. 447 | The default value is _25_, the minimum value is _5_ and hte maximum 448 | value is _100_. 449 | 450 | 451 | class **Node** 452 | 453 | _Implements [node](https://nemproject.github.io/#block-chain-related-requests ) related methods from API._ 454 | 455 | 456 | #### Methods: 457 | 458 | - **\_\_init\_\_**(self, client) 459 | 460 | Initialize self. See help(type(self)) for accurate signature. 461 | - **client**: Client instance 462 | 463 | - **boot**(self, boot\_node\_request) 464 | 465 | Boots the local node and thus assign an account (the identity) to 466 | the local node. 467 | In case the node has already been booted, NIS will return a 468 | JSON error object. 469 | 470 | - **boot\_node\_request**: A [BootNodeRequest](https://nemproject.github.io/#bootNodeRequest) JSON object. 471 | 472 | - **experiences**(self) 473 | 474 | Gets an array of node experiences from another node. 475 | In case the node has not been booted yet, NIS will return a 476 | JSON error object. 477 | 478 | - **extended_info**(self) 479 | 480 | Gets extended information about a node. 481 | In case the node has not been booted yet, NIS will return a 482 | JSON error object. 483 | 484 | - **info**(self) 485 | 486 | Gets basic information about a node. 487 | In case the node has not been booted yet, NIS will return a 488 | JSON error. 489 | 490 | - **max\_chain\_height**(self) 491 | 492 | Requests the chain height from every node in the active node list 493 | and returns the maximum height seen. 494 | In case the node has not been booted yet, NIS will return a JSON 495 | error object. 496 | 497 | - **peer\_list\_active**(self) 498 | 499 | Gets an array of active nodes in the neighborhood that are selected for 500 | broadcasts. 501 | In case the node has not been booted yet, NIS will return a 502 | JSON error object. 503 | 504 | - **peer\_list\_all**(self) 505 | 506 | Gets an array of all known nodes in the neighborhood. 507 | In case the node has not been booted yet, NIS will return a 508 | JSON error object. 509 | 510 | - **peer\_list\_reachable**(self) 511 | 512 | Gets an array of all nodes with status 'active' in the neighborhood. 513 | In case the node has not been booted yet, NIS will return a 514 | JSON error object. 515 | 516 | 517 | ### class **Transaction** 518 | 519 | _Implements [transaction](https://nemproject.github.io/#initiating-transactions ) related methods from API. _ 520 | _According to documentation, should be used with care!_ 521 | 522 | #### Methods: 523 | 524 | - **\_\_init\_\_**(self, client) 525 | 526 | Initialize self. See `help(type(self))` for accurate signature. 527 | - **client**: Client instance 528 | 529 | - **announce**(self, request_announce) 530 | 531 | Creates and broadcasts a transaction. The private key is not involved. 532 | 533 | - **request_announce**: A [**RequestAnnounce**](https://nemproject.github.io/#requestAnnounce) JSON object. 534 | 535 | 536 | - **prepare_announce**(self, request_announce) 537 | 538 | Creates and broadcasts a transaction. Since this request involves the private key of an account, it should only be sent to a local NIS. There are various errors that can occur due to failure of transaction validation. 539 | - **request_announce**: A [**requestPrepareAnnounce**](https://nemproject.github.io/#requestPrepareAnnounce) JSON object. 540 | 541 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Oleksii Semeshchuk 2 | Alex Huszagh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Oleksii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-nis-client 2 | 3 | Python client for [NEM NIS API](https://nemproject.github.io). 4 | 5 | ### Prerequisites 6 | 7 | Install all required dependencies into your `virtualenv` (if you want to use it): 8 | 9 | `pip install git+https://github.com/semolex/nis-python-client` 10 | 11 | 12 | ### Description 13 | Client implements methods from NEM API by calling appropriate method. 14 | Method names are consist of API parent parts and related resources (where hyphens and slashes replaced with underscore). 15 | Its mean, that following API resource - `namespace/root/page` is implemented as `client.namespace.root_page` and `node/info` is `client.node.info`, etc. 16 | 17 | Almost all methods are implemented this way, except few that has either super-long names or duplicate names. Simply discover all methods to see exact namings. 18 | There is 6 API parent resource entities for usage: 19 | * Account 20 | * BlockChain 21 | * Node 22 | * Namespace 23 | * Transaction 24 | * Debug 25 | 26 | Extended code guide for available classes can be found [here](https://github.com/semolex/nis-python-client/blob/master/CODE_INFO.md). 27 | 28 | Few call can be used directly from `Client` instance. 29 | To perform calls to the NEM NIS API, ensure you have access to running NIS instance. 30 | By default NIS uses `7890` port, you can initialize NIS client with other address. 31 | 32 | ### Usage 33 | 34 | Examples of usage: 35 | ```python 36 | from nemnis import Client, explain_status 37 | 38 | nis = Client() 39 | 40 | hb = nis.heartbeat() 41 | 42 | status = nis.status() 43 | 44 | print(status.json()) 45 | 46 | # you can use following function to get verbose message for status 47 | print(explain_status(status.json())) 48 | 49 | print(hb.status_code) 50 | 51 | print(hb.json()) 52 | 53 | acc = nis.account.get('NCKMNCU3STBWBR7E3XD2LR7WSIXF5IVJIDBHBZQT') 54 | 55 | print(acc.status_code) 56 | 57 | print(acc.json()) 58 | 59 | ### You can connect to other nodes just by passing it address: 60 | new_client = Client('http://157.7.223.222:7890') 61 | 62 | new_hb = new_client.heartbeat() 63 | 64 | print(new_hb.status_code) 65 | 66 | print(new_hb.json()) 67 | ``` 68 | All responses are `requests.Response` objects from [requests](http://docs.python-requests.org/en/master/) library. 69 | You can perform all required manipulations after receiving them: convert to 70 | JSON, check status code etc. 71 | 72 | You also can perform call via `call` method from Client: 73 | 74 | ```python 75 | from nemnis import Client 76 | 77 | nis = Client() 78 | nis.call('GET', 'heartbeat') 79 | nis.call('GET', 'account/get', params={'address':'SOMEADDRESS'}) 80 | nis.call('POST', 'local/chain/blocks-after', payload={'height': 100}) 81 | ``` 82 | Also, each parent resource can be initialized separately by passing client instance as parameter: 83 | 84 | ```python 85 | from nemnis import Client, Node 86 | 87 | nis = Client() 88 | 89 | node = Node(nis) 90 | 91 | node_info = node.info() 92 | 93 | print(node_info.status_code) 94 | 95 | print(node_info.json()) 96 | ``` 97 | 98 | ### Asynchronous Usage 99 | 100 | On Python 3.4.2 and above, the python-nis-client supports asynchronous requests using the `aiohttp` library. Each method returns an asyncio coroutine returning a response object; otherwise, the API is identical to the standard client. 101 | 102 | Three helper functions are also provided for the asynchronous API: 103 | - `loop()` Returns the asyncio event loop. 104 | - `run(future)` Evaluate a single asyncio coroutine or future. 105 | - `map(futures)` Evaluate a sequence of asyncio coroutines or futures. 106 | 107 | Examples of usage: 108 | ```python 109 | import nemnis 110 | 111 | # get our client 112 | nis = nemnis.AsyncioClient() 113 | 114 | # request a single NIS resource 115 | hb = nis.run(nis.heartbeat()) 116 | print(nis.run(hb.json())) 117 | 118 | # request many NIS resources asynchronously 119 | hb = nis.heartbeat() 120 | status = nis.status() 121 | responses = nemnis.map([hb, status]) 122 | print(nis.map([i.json() for i in responses])) 123 | ``` 124 | Special thanks to [Alex Huszagh](https://github.com/Alexhuszagh) for contributing async client part. 125 | Have fun! 126 | -------------------------------------------------------------------------------- /nemnis/__init__.py: -------------------------------------------------------------------------------- 1 | __name__ = 'nemnis' 2 | __copyright__ = "2017 Oleksii Semeshchuk" 3 | __license__ = "License: MIT, see LICENSE." 4 | __version__ = "0.0.9" 5 | __author__ = "Oleksii Semeshchuk" 6 | __email__ = "semolex@live.com" 7 | 8 | ''' 9 | nis-python-client 10 | ----------------- 11 | 12 | Python client bindings for the NEM NIS API. 13 | ''' 14 | 15 | from .core import * 16 | from .client import * 17 | try: 18 | from .asyncio import * 19 | except: 20 | pass 21 | -------------------------------------------------------------------------------- /nemnis/asyncio.py: -------------------------------------------------------------------------------- 1 | __copyright__ = "2017 Oleksii Semeshchuk" 2 | __license__ = "License: MIT, see LICENSE." 3 | __version__ = "0.0.9" 4 | __author__ = "Oleksii Semeshchuk" 5 | __email__ = "semolex@live.com" 6 | 7 | ''' 8 | asyncio 9 | ------- 10 | 11 | Module for the asynchronous NIS client. 12 | ''' 13 | 14 | import aiohttp 15 | import asyncio 16 | from .core import AbstractClient, LOCALHOST_ENDPOINT 17 | 18 | __all__ = [ 19 | 'loop', 20 | 'run', 21 | 'map', 22 | 'AsyncioClient', 23 | ] 24 | 25 | 26 | def loop(): 27 | """ 28 | Represents the global event loop. 29 | :return: Event loop for nemnis client. 30 | """ 31 | return asyncio.get_event_loop() 32 | 33 | 34 | def run(future): 35 | """ 36 | Run future until complete. 37 | :return: Return future's result, or raise exception. 38 | """ 39 | return loop().run_until_complete(future) 40 | 41 | 42 | def map(futures): 43 | """ 44 | Asynchronously map list of futures. 45 | :return: Return value of all futures, or raise exception. 46 | """ 47 | return run(asyncio.gather(*futures)) 48 | 49 | 50 | class AsyncioClient(AbstractClient): 51 | """ 52 | Asynchronous variant of the main API client. 53 | Uses a session for connection pooling. 54 | """ 55 | 56 | def __init__(self, endpoint=LOCALHOST_ENDPOINT, max_concurrency=100): 57 | """ 58 | Initialize client. 59 | :param endpoint: address of the NIS. 60 | """ 61 | super(AsyncioClient, self).__init__(endpoint) 62 | self.session = aiohttp.ClientSession(loop=loop()) 63 | self.semaphore = asyncio.Semaphore(max_concurrency) 64 | 65 | def __del__(self): 66 | self.session.close() 67 | 68 | async def call(self, method, name, params=None, payload=None, **kwds): 69 | """ 70 | Make calls to the API via HTTP methods and passed params. 71 | :return: response object 72 | """ 73 | url = self.endpoint + '/' + name 74 | async with self.semaphore: 75 | return await self.session.request(method, url, params=params, 76 | json=payload, **kwds) 77 | -------------------------------------------------------------------------------- /nemnis/client.py: -------------------------------------------------------------------------------- 1 | __copyright__ = "2017 Oleksii Semeshchuk" 2 | __license__ = "License: MIT, see LICENSE." 3 | __version__ = "0.0.9" 4 | __author__ = "Oleksii Semeshchuk" 5 | __email__ = "semolex@live.com" 6 | 7 | ''' 8 | client 9 | ------ 10 | 11 | Module for the synchronous NIS client. 12 | ''' 13 | 14 | import requests 15 | from .core import AbstractClient, LOCALHOST_ENDPOINT 16 | 17 | __all__ = [ 18 | 'Client', 19 | ] 20 | 21 | 22 | class Client(AbstractClient): 23 | """ 24 | Synchronous variant of the main API client. 25 | Uses a session for connection pooling. 26 | """ 27 | 28 | def __init__(self, endpoint=LOCALHOST_ENDPOINT): 29 | """ 30 | Initialize client. 31 | :param endpoint: address of the NIS. 32 | """ 33 | super(Client, self).__init__(endpoint) 34 | self.session = requests.Session() 35 | 36 | def call(self, method, name, params=None, payload=None, **kwds): 37 | """ 38 | Make calls to the API via HTTP methods and passed params. 39 | :return: response object 40 | """ 41 | url = self.endpoint + '/' + name 42 | return self.session.request(method, url, params=params, 43 | json=payload, **kwds) 44 | -------------------------------------------------------------------------------- /nemnis/core.py: -------------------------------------------------------------------------------- 1 | __copyright__ = "2017 Oleksii Semeshchuk" 2 | __license__ = "License: MIT, see LICENSE." 3 | __version__ = "0.0.9" 4 | __author__ = "Oleksii Semeshchuk" 5 | __email__ = "semolex@live.com" 6 | 7 | ''' 8 | core 9 | ---- 10 | 11 | Core module for the NIS client. 12 | 13 | Describes an abstract base class for the client, along with various 14 | helpers, to wrap the NIS API without specifying the actual 15 | API for HTTP requests. Two primary clients wrap this base class: 16 | a synchronous client (`Client`), using `requests` for HTTP requests, 17 | and an asynchronous client (`AsyncioClient`), using `aiohttp` for 18 | HTTP requests. 19 | ''' 20 | 21 | import abc 22 | import six 23 | 24 | __all__ = [ 25 | 'STATUS_LIST', 26 | 'LOCALHOST_ENDPOINT', 27 | 'explain_status', 28 | 'AbstractClient', 29 | 'Account', 30 | 'BlockChain', 31 | 'Node', 32 | 'Namespace', 33 | 'Transaction', 34 | 'Debug', 35 | ] 36 | 37 | 38 | STATUS_LIST = [ 39 | "Unknown status", 40 | "NIS is stopped.", 41 | "NIS is starting.", 42 | "NIS is running.", 43 | "NIS is booting the local node (implies NIS is running).", 44 | "The local node is booted (implies NIS is running).", 45 | "The local node is synchronized (implies NIS is running and the local node is booted)", 46 | "NIS local node does not see any remote NIS node (implies running and booted)", 47 | "NIS is currently loading the block chain from the database. In this state NIS cannot serve any requests" 48 | ] 49 | 50 | LOCALHOST_ENDPOINT = 'http://127.0.0.1:7890' 51 | 52 | 53 | def explain_status(response): 54 | """ 55 | Modifies status response to make it verbose. 56 | Gets status message related to response code. 57 | 58 | :param response: response from `client.status` resource. 59 | :return: dict with modified status response 60 | """ 61 | verbose = STATUS_LIST[response['code']] 62 | response['verbose'] = verbose 63 | return response 64 | 65 | 66 | @six.add_metaclass(abc.ABCMeta) 67 | class AbstractClient(): 68 | """ 69 | Abstract base class that represents main API client. 70 | Make calls to NIS via related methods. 71 | For all required information, please follow: 72 | https://nemproject.github.io/ 73 | All available methods documentation is also can be found there. 74 | """ 75 | 76 | def __init__(self, endpoint): 77 | """ 78 | Initialize client. 79 | :param endpoint: address of the NIS. 80 | """ 81 | self.endpoint = endpoint 82 | 83 | @abc.abstractmethod 84 | def call(self, method, name, params=None, payload=None, **kwds): 85 | """ 86 | Make calls to the API via HTTP methods and passed params. 87 | 88 | :param method: HTTP method of the request. 89 | :param name: name of the API endpoint method. Appends to base URL. 90 | :param params: GET method params, used when method is GET. 91 | :param payload: POST method data, used when method is POST. 92 | :param kwds: any additional arguments. 93 | """ 94 | 95 | def heartbeat(self): 96 | """ 97 | Implements https://nemproject.github.io/#heart-beat-request 98 | Determines if NIS is up and responsive. 99 | If there is no response to this request, NIS is either not running or 100 | is in a state where it can't serve requests. 101 | """ 102 | return self.call('GET', 'heartbeat') 103 | 104 | def status(self): 105 | """ 106 | Implements https://nemproject.github.io/#status-request 107 | Determines the status of NIS. 108 | If there is no response to this request, NIS is either not running or 109 | is in a state where it can't serve requests 110 | """ 111 | return self.call('GET', 'status') 112 | 113 | @property 114 | def account(self): 115 | """ 116 | Represents account related requests from API. 117 | 118 | :return: Account instance for use with it's methods. 119 | """ 120 | return Account(self) 121 | 122 | @property 123 | def blockchain(self): 124 | """ 125 | Represents block chain related requests from API. 126 | 127 | :return: BlockChain instance for use with its methods. 128 | """ 129 | return BlockChain(self) 130 | 131 | @property 132 | def node(self): 133 | """ 134 | Represents node related requests from API. 135 | 136 | :return: Node instance for use with its methods. 137 | """ 138 | return Node(self) 139 | 140 | @property 141 | def namespace(self): 142 | """ 143 | Represents namespaces related requests from API. 144 | 145 | :return: Namespace instance for use with its methods. 146 | """ 147 | return Namespace(self) 148 | 149 | @property 150 | def transaction(self): 151 | """ 152 | Represents transaction related requests methods from API. 153 | 154 | :return: Transaction instance for use with its methods. 155 | """ 156 | return Transaction(self) 157 | 158 | @property 159 | def debug(self): 160 | """ 161 | Represents requests for additional information from NIS. 162 | 163 | :return: Transaction instance for use with its methods. 164 | """ 165 | return Debug(self) 166 | 167 | 168 | class Account: 169 | """ 170 | Implements account related methods from API. 171 | https://nemproject.github.io/#account-related-requests 172 | """ 173 | 174 | def __init__(self, client): 175 | self.name = 'account/' 176 | self.client = client 177 | 178 | def generate(self): 179 | """ 180 | Implements https://nemproject.github.io/#generating-new-account-data 181 | Generates a `KeyPairViewModel` 182 | (https://nemproject.github.io/#keyPairViewModel). 183 | """ 184 | return self.client.call('GET', self.name + 'generate') 185 | 186 | def get(self, address): 187 | """ 188 | Implements https://nemproject.github.io/#requesting-the-account-data 189 | Gets an `AccountMetaDataPair` for an account 190 | (https://nemproject.github.io/#accountMetaDataPair). 191 | If the address parameter is not valid, NIS returns an error. 192 | 193 | :param address: the address of the account. 194 | """ 195 | return self.client.call('GET', self.name + 'get', 196 | params={'address': address}) 197 | 198 | def get_from_public_key(self, pub_key): 199 | """ 200 | Implements https://nemproject.github.io/#requesting-the-account-data 201 | Alternative to `get` method. You can retrieve the account data 202 | by providing the public key for the account. 203 | If the public key parameter is not valid, NIS returns an error 204 | 205 | :param pub_key: The public key of the account as hex string. 206 | """ 207 | return self.client.call('GET', self.name + 'get/from-public-key', 208 | params={'publicKey': pub_key}) 209 | 210 | def get_forwarded(self, address): 211 | """ 212 | Implements https://nemproject.github.io/#requesting-the-original-account-data-for-a-delegate-account 213 | Given a delegate (formerly known as remote) account's address, 214 | gets the AccountMetaDataPair for the account for which the given account 215 | is the delegate account. If the given account address is not a delegate 216 | account for any account, the request returns the `AccountMetaDataPair ` 217 | for the given address 218 | (https://nemproject.github.io/#accountMetaDataPair). 219 | If the address parameter is not valid, NIS returns an error. 220 | 221 | :param address: the address of the delegate account. 222 | 223 | """ 224 | return self.client.call('GET', self.name + 'get/forwarded', 225 | params={'address': address}) 226 | 227 | def get_forwarded_from_public_key(self, pub_key): 228 | """ 229 | Implements https://nemproject.github.io/#requesting-the-original-account-data-for-a-delegate-account 230 | Alternative to `get_forwarded` method. You can retrieve the original 231 | account data by providing the public key of the delegate account. 232 | If the public key parameter is not valid, NIS returns an error. 233 | 234 | :param pub_key: the public key of the account as hex string. 235 | """ 236 | return self.client.call('GET', 237 | self.name + 'get/forwarded/from-public-key', 238 | params={'publicKey': pub_key}) 239 | 240 | def status(self, address): 241 | """ 242 | Implements https://nemproject.github.io/#requesting-the-account-status 243 | Gets the `AccountMetaData` from an account 244 | (https://nemproject.github.io/#accountMetaData). 245 | If the address parameter is not valid, NIS returns an error. 246 | 247 | :param address: the address of the account. 248 | """ 249 | return self.client.call('GET', self.name + 'status', 250 | params={'address': address}) 251 | 252 | def transfers_incoming(self, address, _hash=None, _id=None): 253 | """ 254 | Implements https://nemproject.github.io/#requesting-transaction-data-for-an-account 255 | Gets an array of `TransactionMetaDataPair` objects 256 | (https://nemproject.github.io/#transactionMetaDataPair) 257 | has the address given as parameter to the request. A maximum of 25 258 | transaction meta data pairs is returned. The returned transaction meta 259 | data pairs are sorted in descending order in which they were written 260 | to the database. 261 | If the address parameter is not valid or the id cannot be found in the 262 | database, NIS returns an error. 263 | 264 | :param address: the address of the account. 265 | :param _hash: (optional) the 256 bit sha3 hash of the transaction up to 266 | which transactions are returned. 267 | :param _id: (optional) the transaction id up to which transactions are 268 | returned. 269 | """ 270 | return self.client.call('GET', self.name + 'transfers/incoming', 271 | params={'address': address, 272 | 'hash': _hash, 273 | 'id': _id}) 274 | 275 | def transfers_outgoing(self, address, _hash=None, _id=None): 276 | """ 277 | Implements https://nemproject.github.io/#requesting-transaction-data-for-an-account 278 | Gets an array of transaction meta data pairs where the recipient has 279 | the address given as parameter to the request. 280 | A maximum of 25 transaction meta data pairs is returned. 281 | f the address parameter is not valid or the id cannot be found in the 282 | database, NIS returns an error. 283 | 284 | :param address: the address of the account. 285 | :param _hash: (optional) the 256 bit sha3 hash of the transaction up to 286 | which transactions are returned. 287 | :param _id: (optional) the transaction id up to which transactions are 288 | returned. 289 | """ 290 | return self.client.call('GET', self.name + 'transfers/outgoing', 291 | params={'address': address, 292 | 'hash': _hash, 293 | 'id': _id}) 294 | 295 | def transfers_all(self, address, _hash, _id=None): 296 | """ 297 | Implements https://nemproject.github.io/#requesting-transaction-data-for-an-account 298 | Gets an array of transaction meta data pairs for which an account is 299 | the sender or receiver. A maximum of 25 transaction meta data pairs 300 | is returned. 301 | If the address parameter is not valid or the id cannot be found in the 302 | database, NIS returns an error. 303 | 304 | :param address: the address of the account. 305 | :param _hash: (optional) the 256 bit sha3 hash of the transaction up to 306 | which transactions are returned. 307 | :param _id: (optional) the transaction id up to which transactions are 308 | returned. 309 | """ 310 | return self.client.call('GET', self.name + 'transfers/all', 311 | params={'address': address, 312 | 'hash': _hash, 313 | 'id': _id}) 314 | 315 | def unconfirmed_transactions(self, address): 316 | """ 317 | Implements https://nemproject.github.io/#requesting-transaction-data-for-an-account 318 | Gets the array of transactions for which an account is the sender or 319 | receiver and which have not yet been included in a block. The returned 320 | structure is UnconfirmedTransactionMetaDataPair 321 | (https://nemproject.github.io/#unconfirmedTransactionMetaDataPair) 322 | If the address parameter is not valid, NIS returns an error. 323 | 324 | :param address: the address of the account. 325 | """ 326 | return self.client.call('GET', self.name + 'unconfirmedTransactions', 327 | params={'address': address}) 328 | 329 | def _transfers_incoming(self, private_key, _hash=None, _id=None): 330 | """ 331 | Implements https://nemproject.github.io/#transaction-data-with-decoded-messages 332 | The request returns outgoing transactions as described in the 333 | `transfers_incoming` method. The only difference is that if a 334 | transaction contains an encoded message, this message will be decoded 335 | before it is sent to the requester. 336 | If the private key is not supplied, NIS returns an error. 337 | 338 | :param private_key: the private key as hexadecimal string. 339 | :param (optional) hash value. 340 | :param (optional) transaction id. 341 | """ 342 | return self.client.call('POST', 'local/transfers/incoming', 343 | payload={'value': private_key, 344 | 'hash': _hash, 345 | 'id': _id}) 346 | 347 | def _transfers_outgoing(self, private_key, _hash=None, _id=None): 348 | """ 349 | Implements https://nemproject.github.io/#transaction-data-with-decoded-messages 350 | The request returns outgoing transactions as described in the 351 | `transfers_outgoing` method. The only difference is that if a 352 | transaction contains an encoded message, this message will be decoded 353 | before it is sent to the requester. 354 | If the private key is not supplied, NIS returns an error. 355 | 356 | :param private_key: the private key as hexadecimal string. 357 | :param (optional) hash value. 358 | :param (optional) transaction id. 359 | """ 360 | return self.client.call('POST', 'local/transfers/outgoing', 361 | payload={'value': private_key, 362 | 'hash': _hash, 363 | 'id': _id}) 364 | 365 | def _transfers_all(self, private_key, _hash=None, _id=None): 366 | """ 367 | Implements https://nemproject.github.io/#transaction-data-with-decoded-messages 368 | The request returns all transactions as described in the `transfers_all` 369 | method. The only difference is that if a transaction contains an 370 | encoded message, this message will be decoded before it is sent 371 | to the requester. 372 | If the private key is not supplied, NIS returns an error. 373 | 374 | :param private_key: the private key as hexadecimal string. 375 | :param (optional) hash value. 376 | :param (optional) transaction id. 377 | """ 378 | return self.client.call('POST', 'local/transfers/all', 379 | payload={'value': private_key, 380 | 'hash': _hash, 381 | 'id': _id}) 382 | 383 | def harvests(self, address, _hash): 384 | """ 385 | Implements https://nemproject.github.io/#requesting-harvest-info-data-for-an-account 386 | Gets an array of harvest info objects for an account. 387 | If the address parameter is not valid or the hash cannot be found in 388 | the database, NIS returns an error. 389 | 390 | :param address: the address of the account. 391 | :param _hash: the 256 bit sha3 hash of the block up to which harvested 392 | blocks are returned. 393 | """ 394 | return self.client.call('GET', self.name + 'harvests', 395 | params={'address': address, 'hash': _hash}) 396 | 397 | def importances(self): 398 | """ 399 | Implements https://nemproject.github.io/#retrieving-account-importances-for-accounts 400 | Gets an array of account importance view model objects. 401 | """ 402 | return self.client.call('GET', self.name + 'importances') 403 | 404 | def namespace_page(self, address, parent, _id, page_size=None): 405 | """ 406 | Implements https://nemproject.github.io/#retrieving-namespaces-that-an-account-owns 407 | Gets an array of namespace objects for a given account address. 408 | If `parent` param is supplied, only sub-namespaces of the parent 409 | namespace are returned. 410 | NIS returns an error if the address or the parent (if supplied) 411 | is invalid. 412 | 413 | :param address: the address of the account. 414 | :param parent: (optional) parent namespace id. 415 | :param _id: (optional) namespace database id up to which namespaces 416 | are returned. 417 | :param page_size: (optional) number of namespaces to be returned. 418 | """ 419 | return self.client.call('GET', self.name + 'namespace/page', 420 | params={'address': address, 421 | 'parent': parent, 422 | 'id': _id, 423 | 'pageSize': page_size}) 424 | 425 | def mosaic_definition_page(self, address, parent, _id): 426 | """ 427 | Implements https://nemproject.github.io/#retrieving-mosaic-definitions-that-an-account-has-created 428 | Gets an array of mosaic definition objects for a given account address. 429 | If `parent` param is supplied, only mosaic definitions for the given 430 | parent namespace are returned. 431 | NIS returns an error if the address, the parent (if supplied) 432 | or the id (if supplied) is invalid. 433 | 434 | :param address: the address of the account. 435 | :param parent: (optional) parent namespace id. 436 | :param _id: (optional) mosaic definition database id up to which mosaic 437 | definitions are returned. 438 | """ 439 | return self.client.call('GET', self.name + 'mosaic/definition/page', 440 | params={'address': address, 441 | 'parent': parent, 442 | 'id': _id}) 443 | 444 | def mosaic_owned(self, address): 445 | """ 446 | Implements https://nemproject.github.io/#retrieving-mosaics-that-an-account-owns 447 | Gets an array of mosaic objects for a given account address. 448 | NIS returns an error if the address is invalid. 449 | 450 | :param address: the address of the account. 451 | """ 452 | return self.client.call('GET', self.name + 'mosaic/owned', 453 | params={'address': address}) 454 | 455 | def unlock(self, private_key): 456 | """ 457 | Implements https://nemproject.github.io/#locking-and-unlocking-accounts 458 | Unlocks an account (starts harvesting). 459 | Request return an error if the private key does not correspond to a 460 | known account or the account is not allowed to harvest. 461 | 462 | :param private_key: A PrivateKey JSON object: 463 | (https://nemproject.github.io/#privateKey) 464 | """ 465 | return self.client.call('POST', self.name + 'unlock', 466 | payload={'value': private_key}) 467 | 468 | def lock(self, private_key): 469 | """ 470 | Implements https://nemproject.github.io/#locking-and-unlocking-accounts 471 | Locks an account (stops harvesting). 472 | Request return an error if the private key does not correspond to a 473 | known account or the account is not allowed to harvest. 474 | 475 | :param private_key: A PrivateKey JSON object: 476 | (https://nemproject.github.io/#privateKey) 477 | """ 478 | return self.client.call('POST', self.name + 'lock', 479 | payload={'value': private_key}) 480 | 481 | def unlocked_info(self): 482 | """ 483 | Implements https://nemproject.github.io/#retrieving-the-unlock-info 484 | Gives information about the maximum number of allowed harvesters and 485 | how many harvesters are already using the node. 486 | """ 487 | return self.client.call('POST', self.name + 'unlocked/info') 488 | 489 | def historical_get(self, address, start_height, end_height, inc): 490 | """ 491 | Implements https://nemproject.github.io/#retrieving-historical-account-data 492 | Gets historical information for an account. 493 | To turn on this feature for your NIS, you need to add 494 | `HISTORICAL_ACCOUNT_DATA` to the list of optional features in the file 495 | `config.properties`. 496 | If the address is invalid, the `start_height` is larger than the 497 | `end_height`, the increment is not a positive or the request results in 498 | more than 1000 data points an error is returned. 499 | 500 | :param address: the address of the account. 501 | :param start_height: the block height from which on the data 502 | should be supplied. 503 | :param end_height: the block height up to which the data should be 504 | supplied. The end height must be greater than or equal to the 505 | start height. 506 | :param inc: The value by which the height is incremented between each 507 | data point. The value must be greater than 0. 508 | NIS can supply up to 1000 data points with one request. 509 | Requesting more than 1000 data points results in an error. 510 | """ 511 | return self.client.call('GET', self.name + 'historical/get', 512 | params={'address': address, 513 | 'startHeight': start_height, 514 | 'endHeight': end_height, 515 | 'increment': inc}) 516 | 517 | 518 | class BlockChain: 519 | """ 520 | Implements block chain related methods from API. 521 | https://nemproject.github.io/#block-chain-related-requests 522 | """ 523 | 524 | def __init__(self, client): 525 | self.name = 'chain/' 526 | self.client = client 527 | 528 | def height(self): 529 | """ 530 | Gets the current height of the block chain. 531 | """ 532 | return self.client.call('GET', self.name + 'height') 533 | 534 | def score(self): 535 | """ 536 | Gets the current score of the block chain. 537 | """ 538 | return self.client.call('GET', self.name + 'score') 539 | 540 | def last_block(self): 541 | """ 542 | Gets the current last block of the chain. 543 | 544 | """ 545 | return self.client.call('GET', self.name + 'last-block') 546 | 547 | def at_public(self, block_height): 548 | """ 549 | Gets a block from the chain that has the given height. 550 | If the block with the specified height cannot be found in the database, 551 | NIS will return a JSON error object. 552 | 553 | :param block_height: A `BlockHeight` JSON object 554 | (https://nemproject.github.io/#blockHeight). 555 | """ 556 | return self.client.call('POST', 'block/at/public', payload={ 557 | 'height': block_height 558 | }) 559 | 560 | def local_chain_blocks_after(self, block_height): 561 | """ 562 | Gets up to 10 blocks after given block height from the chain. 563 | The returned data is an array of `ExplorerBlockViewModel` JSON objects 564 | (https://nemproject.github.io/#explorerBlockViewModel). 565 | If the block height supplied is not positive, NIS will return a 566 | JSON error object. 567 | 568 | :param block_height: A `BlockHeight` JSON object 569 | (https://nemproject.github.io/#blockHeight). 570 | """ 571 | return self.client.call('POST', 'local/chain/blocks-after', payload={ 572 | 'height': block_height 573 | }) 574 | 575 | 576 | class Node: 577 | """ 578 | Implements node related methods from API. 579 | https://nemproject.github.io/#node-related-requests 580 | """ 581 | 582 | def __init__(self, client): 583 | self.name = 'node/' 584 | self.client = client 585 | 586 | def info(self): 587 | """ 588 | Gets basic information about a node. 589 | In case the node has not been booted yet, NIS will return a 590 | JSON error object. 591 | """ 592 | return self.client.call('GET', self.name + 'info') 593 | 594 | def extended_info(self): 595 | """ 596 | Gets extended information about a node. 597 | In case the node has not been booted yet, NIS will return a 598 | JSON error object. 599 | """ 600 | return self.client.call('GET', self.name + 'extended-info') 601 | 602 | def peer_list_all(self): 603 | """ 604 | Gets an array of all known nodes in the neighborhood. 605 | n case the node has not been booted yet, NIS will return a 606 | JSON error object. 607 | """ 608 | return self.client.call('GET', self.name + 'peer-list/all') 609 | 610 | def peer_list_reachable(self): 611 | """ 612 | Gets an array of all nodes with status 'active' in the neighborhood. 613 | In case the node has not been booted yet, NIS will return a 614 | JSON error object. 615 | """ 616 | return self.client.call('GET', self.name + 'peer-list/reachable') 617 | 618 | def peer_list_active(self): 619 | """ 620 | Gets an array of active nodes in the neighborhood that are selected for 621 | broadcasts. 622 | In case the node has not been booted yet, NIS will return a 623 | JSON error object. 624 | """ 625 | return self.client.call('GET', self.name + 'peer-list/active') 626 | 627 | def max_chain_height(self): 628 | """ 629 | Requests the chain height from every node in the active node list 630 | and returns the maximum height seen. 631 | In case the node has not been booted yet, NIS will return a JSON 632 | error object. 633 | """ 634 | return self.client.call('GET', 635 | self.name + 'active-peers/max-chain-height') 636 | 637 | def experiences(self): 638 | """ 639 | Gets an array of node experiences from another node. 640 | In case the node has not been booted yet, NIS will return a 641 | JSON error object. 642 | """ 643 | return self.client.call('GET', 644 | self.name + 'experiences') 645 | 646 | def boot(self, boot_node_request): 647 | """ 648 | Boots the local node and thus assign an account (the identity) to 649 | the local node. 650 | In case the node has already been booted, NIS will return a 651 | JSON error object. 652 | 653 | :param boot_node_request: A `BootNodeRequest` JSON object 654 | (https://nemproject.github.io/#bootNodeRequest) 655 | """ 656 | return self.client.call('POST', 657 | self.name + 'boot', payload=boot_node_request) 658 | 659 | 660 | class Namespace: 661 | """ 662 | Implements namespace related methods from API. 663 | https://nemproject.github.io/#namespaces-and-mosaics 664 | """ 665 | 666 | def __init__(self, client): 667 | self.name = 'namespace/' 668 | self.client = client 669 | 670 | def root_page(self, _id=None, page_size=25): 671 | """ 672 | Gets the root namespaces. 673 | 674 | :param _id: (optional) the topmost namespace database id up to which 675 | root namespaces are returned 676 | :param page_size: (optional )the number of namespace objects to be 677 | returned for each request. The parameter is optional. 678 | The default value is 25, the minimum value is 5 and hte maximum 679 | value is 100. 680 | """ 681 | return self.client.call('GET', 682 | self.name + 'root/page', 683 | params={'id': _id, 'pageSize': page_size}) 684 | 685 | def namespace(self, namespace): 686 | """ 687 | Gets the namespace with given id. 688 | 689 | :param namespace: the namespace id. 690 | """ 691 | return self.client.call('GET', 692 | self.name, params={'namespace': namespace}) 693 | 694 | def mosaic_definition_page(self, namespace, _id=None, pagesize=25): 695 | """ 696 | Gets the mosaic definitions for a given namespace. 697 | 698 | :param namespace: the namespace id. 699 | :param _id: (optional) the topmost mosaic definition database id up to 700 | which root mosaic definitions are returned. 701 | The parameter is optional. If not supplied the most recent 702 | mosaic definitiona are returned. 703 | :param pagesize: he number of mosaic definition objects to be returned 704 | for each request. The parameter is optional. The default value 705 | is 25, the minimum value is 5 and hte maximum value is 100. 706 | """ 707 | return self.client.call('GET', 708 | self.name + 'mosaic/definition/page', 709 | params={'namespace': namespace, 'id': _id, 710 | 'pagesize': pagesize}) 711 | 712 | 713 | class Transaction: 714 | """ 715 | Implements transaction related methods from API. 716 | According to documentation, should be used with care! 717 | https://nemproject.github.io/#initiating-transactions 718 | """ 719 | 720 | def __init__(self, client): 721 | self.name = 'transaction/' 722 | self.client = client 723 | 724 | def prepare_announce(self, request_announce): 725 | """ 726 | Creates and broadcasts a transaction. Since this request involves t 727 | he private key of an account, it should only be sent to a local NIS. 728 | There are various errors that can occur due to failure of transaction 729 | validation. 730 | :param request_announce: `requestPrepareAnnounce` JSON object 731 | (https://nemproject.github.io/#requestPrepareAnnounce) 732 | """ 733 | return self.client.call('POST', 734 | self.name + 'prepare-announce', 735 | payload=request_announce) 736 | 737 | def announce(self, request_announce): 738 | """ 739 | Creates and broadcasts a transaction. The private key is not involved. 740 | 741 | :param request_announce: A `RequestAnnounce` JSON object 742 | (https://nemproject.github.io/#requestAnnounce) 743 | """ 744 | return self.client.call('POST', 745 | self.name + 'announce', 746 | payload=request_announce) 747 | 748 | 749 | class Debug: 750 | """ 751 | Implements requests for additional information from NIS. 752 | https://nemproject.github.io/#requests-for-additional-information-from-NIS 753 | """ 754 | 755 | def __init__(self, client): 756 | self.name = 'debug/' 757 | self.client = client 758 | 759 | def time_synchronization(self): 760 | """ 761 | Gets an array of time synchronization results. 762 | You can monitor the change in network time with this information. 763 | """ 764 | return self.client.call('GET', self.name + 'time-synchronization') 765 | 766 | def connections_incoming(self): 767 | """ 768 | Gets an audit collection of incoming calls. 769 | You can monitor the outstanding and recent incoming requests with 770 | this information. 771 | """ 772 | return self.client.call('GET', self.name + 'connections/incoming') 773 | 774 | def connections_outgoing(self): 775 | """ 776 | Gets an audit collection of outgoing calls. 777 | You can monitor the outstanding and recent outgoing requests with 778 | this information. 779 | """ 780 | return self.client.call('GET', self.name + 'connections/outgoing') 781 | 782 | def timers(self): 783 | """ 784 | Gets an array of task monitor structures. 785 | You can monitor the statistics for periodic tasks with 786 | this information. 787 | """ 788 | return self.client.call('GET', self.name + '/timers') 789 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.20.0 2 | six 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __copyright__ = "2017 Oleksii Semeshchuk" 3 | __license__ = "License: MIT, see LICENSE." 4 | 5 | ''' 6 | setup 7 | _____ 8 | ''' 9 | 10 | from setuptools import setup 11 | import unittest 12 | 13 | DESCRIPTION = 'Python client for NEM NIS API (https://nemproject.github.io)' 14 | 15 | CLASSIFIERS = [ 16 | 'Development Status :: 3 - Alpha', 17 | 'License :: OSI Approved :: Apache Software License', 18 | 'Topic :: Software Development :: Libraries :: Python Modules', 19 | 'Intended Audience :: Developers', 20 | 'Natural Language :: English', 21 | 'Operating System :: Unix', 22 | 'Operating System :: POSIX :: Linux', 23 | 'Operating System :: MacOS :: MacOS X', 24 | 'Operating System :: Microsoft :: Windows', 25 | 'Programming Language :: Python', 26 | 'Programming Language :: Python :: 2', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.5', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Topic :: Internet :: WWW/HTTP', 32 | 'Framework :: AsyncIO', 33 | ] 34 | 35 | def test_suite(): 36 | loader = unittest.TestLoader() 37 | suite = loader.discover('test', pattern='test_*.py') 38 | return suite 39 | 40 | 41 | setup( 42 | name='nis-python-client', 43 | version='0.0.9', 44 | description=DESCRIPTION, 45 | classifiers=CLASSIFIERS, 46 | author='semolex (Oleksii Semeshchuk)', 47 | author_email='semolex@live.com', 48 | packages=['nemnis'], 49 | url='https://github.com/semolex/nis-python-client', 50 | license='MIT', 51 | test_suite='setup.test_suite', 52 | zip_safe=True, 53 | install_requires=[ 54 | 'requests==2.20.0', 55 | 'six', 56 | ], 57 | test_requires=[ 58 | 'requests-mock==1.4.0', 59 | ], 60 | ) 61 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semolex/nis-python-client/e2e8f8d6546b51930e825135bba81b971fcef28a/test/__init__.py -------------------------------------------------------------------------------- /test/test_account.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import requests 4 | import requests_mock 5 | from nemnis import Client, Account 6 | 7 | 8 | class TestAccount(TestCase): 9 | def setUp(self): 10 | self.client = Client(endpoint='mock://127.0.0.1:7890') 11 | 12 | def test_client_is_used(self): 13 | account = Account(self.client) 14 | self.assertEqual(account.client, self.client) 15 | self.assertEqual(account.name, 'account/') 16 | 17 | def test_generate(self): 18 | with requests_mock.Mocker() as m: 19 | m.get('mock://127.0.0.1:7890/account/generate', status_code=200) 20 | resp = self.client.account.generate() 21 | self.assertIsInstance(resp, requests.Response) 22 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/account/generate') 23 | self.assertEqual(resp.status_code, 200) 24 | 25 | def test_get(self): 26 | with requests_mock.Mocker() as m: 27 | m.get('mock://127.0.0.1:7890/account/get', status_code=200) 28 | resp = self.client.account.get('TESTADDRESS') 29 | self.assertIsInstance(resp, requests.Response) 30 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/account/get') 31 | self.assertEqual(resp.status_code, 200) 32 | 33 | def test_get_from_public_key(self): 34 | with requests_mock.Mocker() as m: 35 | m.get('mock://127.0.0.1:7890/account/get/from-public-key', 36 | status_code=200) 37 | resp = self.client.account.get_from_public_key('testpublickkey') 38 | self.assertIsInstance(resp, requests.Response) 39 | self.assertEqual(resp.url, 40 | 'mock://127.0.0.1:7890/account/get/from-public-key') 41 | self.assertEqual(resp.status_code, 200) 42 | 43 | def test_get_forwarded(self): 44 | with requests_mock.Mocker() as m: 45 | m.get('mock://127.0.0.1:7890/account/get/forwarded', 46 | status_code=200) 47 | resp = self.client.account.get_forwarded('TESTADDRESS') 48 | self.assertIsInstance(resp, requests.Response) 49 | self.assertEqual(resp.url, 50 | 'mock://127.0.0.1:7890/account/get/forwarded') 51 | self.assertEqual(resp.status_code, 200) 52 | 53 | def test_get_forwarded_from_public_key(self): 54 | with requests_mock.Mocker() as m: 55 | m.get('mock://127.0.0.1:7890/account/get/forwarded/from-public-key', 56 | status_code=200) 57 | resp = self.client.account.get_forwarded_from_public_key( 58 | 'testpublickkey') 59 | self.assertIsInstance(resp, requests.Response) 60 | self.assertEqual(resp.url, 61 | 'mock://127.0.0.1:7890/account/get/forwarded/from-public-key') 62 | self.assertEqual(resp.status_code, 200) 63 | 64 | def test_status(self): 65 | with requests_mock.Mocker() as m: 66 | m.get('mock://127.0.0.1:7890/account/status', status_code=200) 67 | resp = self.client.account.status('TESTADDRESS') 68 | self.assertIsInstance(resp, requests.Response) 69 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/account/status') 70 | self.assertEqual(resp.status_code, 200) 71 | 72 | def test_transfers_incoming(self): 73 | with requests_mock.Mocker() as m: 74 | m.get('mock://127.0.0.1:7890/account/transfers/incoming', 75 | status_code=200) 76 | resp = self.client.account.transfers_incoming('TESTADDRESS', 77 | 'testhash', 'id01') 78 | self.assertIsInstance(resp, requests.Response) 79 | self.assertEqual(resp.url, 80 | 'mock://127.0.0.1:7890/account/transfers/incoming') 81 | self.assertEqual(resp.status_code, 200) 82 | 83 | def test_transfers_outgoing(self): 84 | with requests_mock.Mocker() as m: 85 | m.get('mock://127.0.0.1:7890/account/transfers/outgoing', 86 | status_code=200) 87 | resp = self.client.account.transfers_outgoing('TESTADDRESS', 88 | 'testhash', 'id01') 89 | self.assertIsInstance(resp, requests.Response) 90 | self.assertEqual(resp.url, 91 | 'mock://127.0.0.1:7890/account/transfers/outgoing') 92 | self.assertEqual(resp.status_code, 200) 93 | 94 | def test_transfers_all(self): 95 | with requests_mock.Mocker() as m: 96 | m.get('mock://127.0.0.1:7890/account/transfers/all', 97 | status_code=200) 98 | resp = self.client.account.transfers_all('TESTADDRESS', 'testhash', 99 | 'id01') 100 | self.assertIsInstance(resp, requests.Response) 101 | self.assertEqual(resp.url, 102 | 'mock://127.0.0.1:7890/account/transfers/all') 103 | self.assertEqual(resp.status_code, 200) 104 | 105 | def test_unconfirmed_transactions(self): 106 | with requests_mock.Mocker() as m: 107 | m.get('mock://127.0.0.1:7890/account/unconfirmedTransactions', 108 | status_code=200) 109 | resp = self.client.account.unconfirmed_transactions('TESTADDRESS') 110 | self.assertIsInstance(resp, requests.Response) 111 | self.assertEqual(resp.url, 112 | 'mock://127.0.0.1:7890/account/unconfirmedTransactions') 113 | self.assertEqual(resp.status_code, 200) 114 | 115 | def test__transfers_incoming(self): 116 | with requests_mock.Mocker() as m: 117 | m.post('mock://127.0.0.1:7890/local/transfers/incoming', 118 | status_code=200) 119 | resp = self.client.account._transfers_incoming('testprivatekey', 120 | 'testhash', 'id01') 121 | self.assertIsInstance(resp, requests.Response) 122 | self.assertEqual(resp.url, 123 | 'mock://127.0.0.1:7890/local/transfers/incoming') 124 | self.assertEqual(resp.status_code, 200) 125 | 126 | def test__transfers_outgoing(self): 127 | with requests_mock.Mocker() as m: 128 | m.post('mock://127.0.0.1:7890/local/transfers/outgoing', 129 | status_code=200) 130 | resp = self.client.account._transfers_outgoing('testprivatekey', 131 | 'testhash', 'id01') 132 | self.assertIsInstance(resp, requests.Response) 133 | self.assertEqual(resp.url, 134 | 'mock://127.0.0.1:7890/local/transfers/outgoing') 135 | self.assertEqual(resp.status_code, 200) 136 | 137 | def test__transfers_all(self): 138 | with requests_mock.Mocker() as m: 139 | m.post('mock://127.0.0.1:7890/local/transfers/all', status_code=200) 140 | resp = self.client.account._transfers_all('testprivatekey', 141 | 'testhash', 'id01') 142 | self.assertIsInstance(resp, requests.Response) 143 | self.assertEqual(resp.url, 144 | 'mock://127.0.0.1:7890/local/transfers/all') 145 | self.assertEqual(resp.status_code, 200) 146 | 147 | def test_harvests(self): 148 | with requests_mock.Mocker() as m: 149 | m.get('mock://127.0.0.1:7890/account/harvests', status_code=200) 150 | resp = self.client.account.harvests('TESTADDRESS', 'testhash') 151 | self.assertIsInstance(resp, requests.Response) 152 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/account/harvests') 153 | self.assertEqual(resp.status_code, 200) 154 | 155 | def test_importances(self): 156 | with requests_mock.Mocker() as m: 157 | m.get('mock://127.0.0.1:7890/account/importances', status_code=200) 158 | resp = self.client.account.importances() 159 | self.assertIsInstance(resp, requests.Response) 160 | self.assertEqual(resp.url, 161 | 'mock://127.0.0.1:7890/account/importances') 162 | self.assertEqual(resp.status_code, 200) 163 | 164 | def test_namespace_page(self): 165 | with requests_mock.Mocker() as m: 166 | m.get('mock://127.0.0.1:7890/account/namespace/page', 167 | status_code=200) 168 | resp = self.client.account.namespace_page('TESTADDRESS', 169 | 'some.parent', 'id:0') 170 | self.assertIsInstance(resp, requests.Response) 171 | self.assertEqual(resp.url, 172 | 'mock://127.0.0.1:7890/account/namespace/page') 173 | self.assertEqual(resp.status_code, 200) 174 | 175 | def test_mosaic_definition_page(self): 176 | with requests_mock.Mocker() as m: 177 | m.get('mock://127.0.0.1:7890/account/mosaic/definition/page', 178 | status_code=200) 179 | resp = self.client.account.mosaic_definition_page('TESTADDRESS', 180 | 'some.parent', 181 | 'id:0') 182 | self.assertIsInstance(resp, requests.Response) 183 | self.assertEqual(resp.url, 184 | 'mock://127.0.0.1:7890/account/mosaic/definition/page') 185 | self.assertEqual(resp.status_code, 200) 186 | 187 | def test_mosaic_owned(self): 188 | with requests_mock.Mocker() as m: 189 | m.get('mock://127.0.0.1:7890/account/mosaic/owned', status_code=200) 190 | resp = self.client.account.mosaic_owned('TESTADDRESS') 191 | self.assertIsInstance(resp, requests.Response) 192 | self.assertEqual(resp.url, 193 | 'mock://127.0.0.1:7890/account/mosaic/owned') 194 | self.assertEqual(resp.status_code, 200) 195 | 196 | def test_unlock(self): 197 | with requests_mock.Mocker() as m: 198 | m.post('mock://127.0.0.1:7890/account/unlock', status_code=200) 199 | resp = self.client.account.unlock('testprivatekey') 200 | self.assertIsInstance(resp, requests.Response) 201 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/account/unlock') 202 | self.assertEqual(resp.status_code, 200) 203 | 204 | def test_lock(self): 205 | with requests_mock.Mocker() as m: 206 | m.post('mock://127.0.0.1:7890/account/lock', status_code=200) 207 | resp = self.client.account.lock('testprivatekey') 208 | self.assertIsInstance(resp, requests.Response) 209 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/account/lock') 210 | self.assertEqual(resp.status_code, 200) 211 | 212 | def test_unlocked_info(self): 213 | with requests_mock.Mocker() as m: 214 | m.post('mock://127.0.0.1:7890/account/unlocked/info', 215 | status_code=200) 216 | resp = self.client.account.unlocked_info() 217 | self.assertIsInstance(resp, requests.Response) 218 | self.assertEqual(resp.url, 219 | 'mock://127.0.0.1:7890/account/unlocked/info') 220 | self.assertEqual(resp.status_code, 200) 221 | 222 | def test_historical_get(self): 223 | with requests_mock.Mocker() as m: 224 | m.get('mock://127.0.0.1:7890/account/historical/get', 225 | status_code=200) 226 | resp = self.client.account.historical_get('TESTADDRESS', 1, 2, 1) 227 | self.assertIsInstance(resp, requests.Response) 228 | self.assertEqual(resp.url, 229 | 'mock://127.0.0.1:7890/account/historical/get') 230 | self.assertEqual(resp.status_code, 200) 231 | -------------------------------------------------------------------------------- /test/test_blockChain.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import requests 4 | import requests_mock 5 | from nemnis import Client, BlockChain 6 | 7 | 8 | class TestBlockChain(TestCase): 9 | def setUp(self): 10 | self.client = Client(endpoint='mock://127.0.0.1:7890') 11 | 12 | def test_client_is_used(self): 13 | blockchain = BlockChain(self.client) 14 | self.assertEqual(blockchain.client, self.client) 15 | self.assertEqual(blockchain.name, 'chain/') 16 | 17 | def test_height(self): 18 | with requests_mock.Mocker() as m: 19 | m.get('mock://127.0.0.1:7890/chain/height', status_code=200) 20 | resp = self.client.blockchain.height() 21 | self.assertIsInstance(resp, requests.Response) 22 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/chain/height') 23 | self.assertEqual(resp.status_code, 200) 24 | 25 | def test_score(self): 26 | with requests_mock.Mocker() as m: 27 | m.get('mock://127.0.0.1:7890/chain/score', status_code=200) 28 | resp = self.client.blockchain.score() 29 | self.assertIsInstance(resp, requests.Response) 30 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/chain/score') 31 | self.assertEqual(resp.status_code, 200) 32 | 33 | def test_last_block(self): 34 | with requests_mock.Mocker() as m: 35 | m.get('mock://127.0.0.1:7890/chain/last-block', status_code=200) 36 | resp = self.client.blockchain.last_block() 37 | self.assertIsInstance(resp, requests.Response) 38 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/chain/last-block') 39 | self.assertEqual(resp.status_code, 200) 40 | 41 | def test_at_public(self): 42 | with requests_mock.Mocker() as m: 43 | m.post('mock://127.0.0.1:7890/block/at/public', status_code=200) 44 | resp = self.client.blockchain.at_public(100) 45 | self.assertIsInstance(resp, requests.Response) 46 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/block/at/public') 47 | self.assertEqual(resp.status_code, 200) 48 | 49 | def test_local_chain_blocks_after(self): 50 | with requests_mock.Mocker() as m: 51 | m.post('mock://127.0.0.1:7890/local/chain/blocks-after', 52 | status_code=200) 53 | resp = self.client.blockchain.local_chain_blocks_after(100) 54 | self.assertIsInstance(resp, requests.Response) 55 | self.assertEqual(resp.url, 56 | 'mock://127.0.0.1:7890/local/chain/blocks-after') 57 | self.assertEqual(resp.status_code, 200) 58 | -------------------------------------------------------------------------------- /test/test_client.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import requests 4 | import requests_mock 5 | from nemnis import (Client, Account, BlockChain, Node, Namespace, Transaction, 6 | Debug) 7 | 8 | 9 | class TestClient(TestCase): 10 | 11 | def setUp(self): 12 | self.client = Client(endpoint='mock://127.0.0.1:7890') 13 | 14 | def test_call(self): 15 | with requests_mock.Mocker() as m: 16 | m.get('mock://127.0.0.1:7890/test/api', text='ok') 17 | resp = self.client.call('GET', 'test/api') 18 | self.assertIsInstance(resp, requests.Response) 19 | 20 | def test_heartbeat(self): 21 | with requests_mock.Mocker() as m: 22 | m.get('mock://127.0.0.1:7890/heartbeat', status_code=200) 23 | resp = self.client.heartbeat() 24 | self.assertIsInstance(resp, requests.Response) 25 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/heartbeat') 26 | self.assertEqual(resp.status_code, 200) 27 | 28 | def test_status(self): 29 | with requests_mock.Mocker() as m: 30 | m.get('mock://127.0.0.1:7890/status', status_code=200) 31 | resp = self.client.status() 32 | self.assertIsInstance(resp, requests.Response) 33 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/status') 34 | self.assertEqual(resp.status_code, 200) 35 | 36 | def test_account(self): 37 | self.assertIsInstance(self.client.account, Account) 38 | 39 | def test_blockchain(self): 40 | self.assertIsInstance(self.client.blockchain, BlockChain) 41 | 42 | def test_node(self): 43 | self.assertIsInstance(self.client.node, Node) 44 | 45 | def test_namespace(self): 46 | self.assertIsInstance(self.client.namespace, Namespace) 47 | 48 | def test_transaction(self): 49 | self.assertIsInstance(self.client.transaction, Transaction) 50 | 51 | def test_debug(self): 52 | self.assertIsInstance(self.client.debug, Debug) 53 | -------------------------------------------------------------------------------- /test/test_namespace.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import requests 4 | import requests_mock 5 | from nemnis import Client, Namespace 6 | 7 | 8 | class TestNamespace(TestCase): 9 | def setUp(self): 10 | self.client = Client(endpoint='mock://127.0.0.1:7890') 11 | 12 | def test_client_is_used(self): 13 | node = Namespace(self.client) 14 | self.assertEqual(node.client, self.client) 15 | self.assertEqual(node.name, 'namespace/') 16 | 17 | def test_root_page(self): 18 | with requests_mock.Mocker() as m: 19 | m.get('mock://127.0.0.1:7890/namespace/root/page', status_code=200) 20 | resp = self.client.namespace.root_page('id:0', 5) 21 | self.assertIsInstance(resp, requests.Response) 22 | self.assertEqual(resp.url, 23 | 'mock://127.0.0.1:7890/namespace/root/page') 24 | self.assertEqual(resp.status_code, 200) 25 | 26 | def test_namespace(self): 27 | with requests_mock.Mocker() as m: 28 | m.get('mock://127.0.0.1:7890/namespace/', status_code=200) 29 | resp = self.client.namespace.namespace('test.namespace') 30 | self.assertIsInstance(resp, requests.Response) 31 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/namespace/') 32 | self.assertEqual(resp.status_code, 200) 33 | 34 | def test_mosaic_definition_page(self): 35 | with requests_mock.Mocker() as m: 36 | part = 'definition/page' 37 | m.get('mock://127.0.0.1:7890/namespace/mosaic/definition/page', 38 | status_code=200) 39 | resp = self.client.namespace.mosaic_definition_page( 40 | 'test.namespace') 41 | self.assertIsInstance(resp, requests.Response) 42 | self.assertEqual(resp.url, 43 | 'mock://127.0.0.1:7890/namespace/mosaic/' + part) 44 | self.assertEqual(resp.status_code, 200) 45 | -------------------------------------------------------------------------------- /test/test_node.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import requests 4 | import requests_mock 5 | from nemnis import Client, Node 6 | 7 | 8 | class TestNode(TestCase): 9 | def setUp(self): 10 | self.client = Client(endpoint='mock://127.0.0.1:7890') 11 | 12 | def test_client_is_used(self): 13 | node = Node(self.client) 14 | self.assertEqual(node.client, self.client) 15 | self.assertEqual(node.name, 'node/') 16 | 17 | def test_info(self): 18 | with requests_mock.Mocker() as m: 19 | m.get('mock://127.0.0.1:7890/node/info', status_code=200) 20 | resp = self.client.node.info() 21 | self.assertIsInstance(resp, requests.Response) 22 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/node/info') 23 | self.assertEqual(resp.status_code, 200) 24 | 25 | def test_extended_info(self): 26 | with requests_mock.Mocker() as m: 27 | m.get('mock://127.0.0.1:7890/node/extended-info', status_code=200) 28 | resp = self.client.node.extended_info() 29 | self.assertIsInstance(resp, requests.Response) 30 | self.assertEqual(resp.url, 31 | 'mock://127.0.0.1:7890/node/extended-info') 32 | self.assertEqual(resp.status_code, 200) 33 | 34 | def test_peer_list_all(self): 35 | with requests_mock.Mocker() as m: 36 | m.get('mock://127.0.0.1:7890/node/peer-list/all', status_code=200) 37 | resp = self.client.node.peer_list_all() 38 | self.assertIsInstance(resp, requests.Response) 39 | self.assertEqual(resp.url, 40 | 'mock://127.0.0.1:7890/node/peer-list/all') 41 | self.assertEqual(resp.status_code, 200) 42 | 43 | def test_peer_list_reachable(self): 44 | with requests_mock.Mocker() as m: 45 | m.get('mock://127.0.0.1:7890/node/peer-list/reachable', 46 | status_code=200) 47 | resp = self.client.node.peer_list_reachable() 48 | self.assertIsInstance(resp, requests.Response) 49 | self.assertEqual(resp.url, 50 | 'mock://127.0.0.1:7890/node/peer-list/reachable') 51 | self.assertEqual(resp.status_code, 200) 52 | 53 | def test_peer_list_active(self): 54 | with requests_mock.Mocker() as m: 55 | m.get('mock://127.0.0.1:7890/node/peer-list/active', 56 | status_code=200) 57 | resp = self.client.node.peer_list_active() 58 | self.assertIsInstance(resp, requests.Response) 59 | self.assertEqual(resp.url, 60 | 'mock://127.0.0.1:7890/node/peer-list/active') 61 | self.assertEqual(resp.status_code, 200) 62 | 63 | def test_max_chain_height(self): 64 | with requests_mock.Mocker() as m: 65 | m.get('mock://127.0.0.1:7890/node/active-peers/max-chain-height', 66 | status_code=200) 67 | resp = self.client.node.max_chain_height() 68 | self.assertIsInstance(resp, requests.Response) 69 | self.assertEqual( 70 | resp.url, 71 | 'mock://127.0.0.1:7890/node/active-peers/max-chain-height' 72 | ) 73 | self.assertEqual(resp.status_code, 200) 74 | 75 | def test_experiences(self): 76 | with requests_mock.Mocker() as m: 77 | m.get('mock://127.0.0.1:7890/node/experiences', status_code=200) 78 | resp = self.client.node.experiences() 79 | self.assertIsInstance(resp, requests.Response) 80 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/node/experiences') 81 | self.assertEqual(resp.status_code, 200) 82 | 83 | def test_boot(self): 84 | with requests_mock.Mocker() as m: 85 | m.post('mock://127.0.0.1:7890/node/boot', status_code=200) 86 | resp = self.client.node.boot({'testbootobject': 'data'}) 87 | self.assertIsInstance(resp, requests.Response) 88 | self.assertEqual(resp.url, 'mock://127.0.0.1:7890/node/boot') 89 | self.assertEqual(resp.status_code, 200) 90 | -------------------------------------------------------------------------------- /test/test_transaction.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import requests 4 | import requests_mock 5 | from nemnis import Client, Transaction 6 | 7 | 8 | class TestTransaction(TestCase): 9 | def setUp(self): 10 | self.client = Client(endpoint='mock://127.0.0.1:7890') 11 | 12 | def test_client_is_used(self): 13 | transaction = Transaction(self.client) 14 | self.assertEqual(transaction.client, self.client) 15 | self.assertEqual(transaction.name, 'transaction/') 16 | 17 | def test_prepare_announce(self): 18 | with requests_mock.Mocker() as m: 19 | m.post('mock://127.0.0.1:7890/transaction/prepare-announce', 20 | status_code=200) 21 | resp = self.client.transaction.prepare_announce( 22 | {'testtrasaction': 'data'}) 23 | self.assertIsInstance(resp, requests.Response) 24 | self.assertEqual( 25 | resp.url, 26 | 'mock://127.0.0.1:7890/transaction/prepare-announce' 27 | ) 28 | self.assertEqual(resp.status_code, 200) 29 | 30 | def test_announce(self): 31 | with requests_mock.Mocker() as m: 32 | m.post('mock://127.0.0.1:7890/transaction/announce', 33 | status_code=200) 34 | resp = self.client.transaction.announce({'testtrasaction': 'data'}) 35 | self.assertIsInstance(resp, requests.Response) 36 | self.assertEqual(resp.url, 37 | 'mock://127.0.0.1:7890/transaction/announce') 38 | self.assertEqual(resp.status_code, 200) 39 | --------------------------------------------------------------------------------