diff --git a/.travis.yml b/.travis.yml index b788bcd03..eed1fd928 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,4 +40,5 @@ script: - pip install mock pylint unqlite - pylint lbrynet - PYTHONPATH=. trial tests + - python -m unittest discover tests/integration - rvm use 2.3.1 && gem install danger --version '~> 4.0' && danger diff --git a/CHANGELOG.md b/CHANGELOG.md index 361eb1003..8b49539cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,12 @@ at anytime. * Prevent publish of files with size 0 * Add `dht_status` parameter to `status` to include bandwidth and peer info * + * Positional and flag arguments in lbrynet-cli + * ### Changed - * - * + * Changed keyword arguments in lbrynet-cli to use a -- prefix + * Using the help function in lbrynet-cli no longer requires lbrynet-daemon to be running ### Fixed * diff --git a/lbrynet/lbrynet_daemon/Daemon.py b/lbrynet/lbrynet_daemon/Daemon.py index dae108213..095a9fbb4 100644 --- a/lbrynet/lbrynet_daemon/Daemon.py +++ b/lbrynet/lbrynet_daemon/Daemon.py @@ -990,18 +990,19 @@ class Daemon(AuthJSONRPCServer): ############################################################################ @defer.inlineCallbacks + @AuthJSONRPCServer.flags(session_status="-s", dht_status="-d") def jsonrpc_status(self, session_status=False, dht_status=False): """ - Return daemon status + Get daemon status - Args: - 'session_status' (optional): (bool) true to return session status, - default is false - 'dht_status' (optional): (bool) true to return dht status, - default is false - Returns: - (dict) Daemon status dictionary + Usage: + status [-s] [-d] + + Options: + -s : include session status in results + -d : include dht network and peer status """ + # on startup, the wallet or network won't be available but we still need this call to work has_wallet = self.session and self.session.wallet and self.session.wallet.network local_height = self.session.wallet.network.get_local_height() if has_wallet else 0 @@ -1042,23 +1043,28 @@ class Daemon(AuthJSONRPCServer): response['dht_status'] = self.session.dht_node.get_bandwidth_stats() defer.returnValue(response) + @AuthJSONRPCServer.deprecated('status') def jsonrpc_get_best_blockhash(self): """ DEPRECATED. Use `status blockchain_status=True` instead """ + d = self.jsonrpc_status() d.addCallback(lambda x: self._render_response( x['blockchain_status']['best_blockhash'])) return d + @AuthJSONRPCServer.deprecated('status') def jsonrpc_is_running(self): """ DEPRECATED. Use `status` instead """ + d = self.jsonrpc_status() d.addCallback(lambda x: self._render_response(x['is_running'])) return d + @AuthJSONRPCServer.deprecated('status') def jsonrpc_daemon_status(self): """ DEPRECATED. Use `status` instead @@ -1094,14 +1100,17 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda x: self._render_response(x)) # is this necessary? return d + @AuthJSONRPCServer.deprecated('status') def jsonrpc_is_first_run(self): """ DEPRECATED. Use `status` instead """ + d = self.jsonrpc_status() d.addCallback(lambda x: self._render_response(x['is_first_run'])) return d + @AuthJSONRPCServer.deprecated('status') def jsonrpc_get_lbry_session_info(self): """ DEPRECATED. Use `status` instead @@ -1115,10 +1124,12 @@ class Daemon(AuthJSONRPCServer): })) return d + @AuthJSONRPCServer.deprecated('status') def jsonrpc_get_time_behind_blockchain(self): """ DEPRECATED. Use `status` instead """ + d = self.jsonrpc_status() d.addCallback(lambda x: self._render_response(x['blockchain_status']['blocks_behind'])) return d @@ -1127,8 +1138,9 @@ class Daemon(AuthJSONRPCServer): """ Get lbry version information - Args: - None + Usage: + version + Returns: (dict) Dictionary of lbry version information { @@ -1153,8 +1165,9 @@ class Daemon(AuthJSONRPCServer): """ Report a bug to slack - Args: - 'message': (str) message to send + Usage: + report_bug ( | --message=) + Returns: (bool) true if successful """ @@ -1168,6 +1181,7 @@ class Daemon(AuthJSONRPCServer): ) return self._render_response(True) + @AuthJSONRPCServer.deprecated('settings_get') def jsonrpc_get_settings(self): """ DEPRECATED. Use `settings_get` instead. @@ -1178,12 +1192,16 @@ class Daemon(AuthJSONRPCServer): """ Get daemon settings + Usage: + settings_get + Returns: (dict) Dictionary of daemon settings See ADJUSTABLE_SETTINGS in lbrynet/conf.py for full list of settings """ return self._render_response(conf.settings.get_adjustable_settings_dict()) + @AuthJSONRPCServer.deprecated('settings_set') @AuthJSONRPCServer.auth_required def jsonrpc_set_settings(self, **kwargs): """ @@ -1218,11 +1236,11 @@ class Daemon(AuthJSONRPCServer): """ Return a useful message for an API command - Args: - 'command'(optional): (str) command to retrieve documentation for - Returns: - (str) if given a command, returns documentation about that command - otherwise returns general help message + Usage: + help [ | --command=] + + Options: + , --command= : command to retrieve documentation for """ if command is None: @@ -1248,28 +1266,32 @@ class Daemon(AuthJSONRPCServer): """ Return a list of available commands + Usage: + commands + Returns: (list) list of available commands """ - return self._render_response(sorted( - [command for command in self.callable_methods.keys() - if 'DEPRECATED' not in getattr(self, "jsonrpc_" + command).__doc__] - )) + return self._render_response(sorted([command for command in self.callable_methods.keys()])) + @AuthJSONRPCServer.deprecated('wallet_balance') def jsonrpc_get_balance(self, address=None, include_unconfirmed=False): """ DEPRECATED. Use `wallet_balance` instead. """ return self.jsonrpc_wallet_balance(address, include_unconfirmed) + @AuthJSONRPCServer.flags(include_unconfirmed='-u') def jsonrpc_wallet_balance(self, address=None, include_unconfirmed=False): """ Return the balance of the wallet - Args: - 'address' (optional): If address is provided only that balance will be given - 'include_unconfirmed' (optional): If set unconfirmed balance will be included in - the only takes effect when address is also provided. + Usage: + wallet_balance [
| --address=
] [-u] + + Options: +
: If provided only the balance for this address will be given + -u : Include unconfirmed Returns: (float) amount of lbry credits in wallet @@ -1280,6 +1302,7 @@ class Daemon(AuthJSONRPCServer): return self._render_response(float( self.session.wallet.get_address_balance(address, include_unconfirmed))) + @AuthJSONRPCServer.deprecated('daemon_stop') def jsonrpc_stop(self): """ DEPRECATED. Use `daemon_stop` instead. @@ -1291,6 +1314,9 @@ class Daemon(AuthJSONRPCServer): """ Stop lbrynet-daemon + Usage: + daemon_stop + Returns: (string) Shutdown message """ @@ -1301,19 +1327,27 @@ class Daemon(AuthJSONRPCServer): defer.returnValue(response) @defer.inlineCallbacks + @AuthJSONRPCServer.flags(full_status='-f') def jsonrpc_file_list(self, **kwargs): """ List files limited by optional filters - Args: - 'name' (optional): (str) filter files by lbry name, - 'sd_hash' (optional): (str) filter files by sd hash, - 'file_name' (optional): (str) filter files by the name in the downloads folder, - 'stream_hash' (optional): (str) filter files by stream hash, - 'claim_id' (optional): (str) filter files by claim id, - 'outpoint' (optional): (str) filter files by claim outpoint, - 'rowid' (optional): (int) filter files by internal row id, - 'full_status': (optional): (bool) if true populate the 'message' and 'size' fields + Usage: + file_list [--sd_hash=] [--file_name=] [--stream_hash=] + [--claim_id=] [--outpoint=] [--rowid=] + [--name=] + [-f] + + Options: + --sd_hash= : get file with matching sd hash + --file_name= : get file with matching file name in the + downloads folder + --stream_hash= : get file with matching stream hash + --claim_id= : get file with matching claim id + --outpoint= : get file with matching claim outpoint + --rowid= : get file with matching row id + --name= : get file with matching associated name claim + -f : full status, populate the 'message' and 'size' fields Returns: (list) List of files @@ -1348,12 +1382,17 @@ class Daemon(AuthJSONRPCServer): defer.returnValue(response) @defer.inlineCallbacks + @AuthJSONRPCServer.flags(force='-f') def jsonrpc_resolve_name(self, name, force=False): """ Resolve stream info from a LBRY name - Args: - 'name': (str) name to look up, do not include lbry:// prefix + Usage: + resolve_name [-f] + + Options: + -f : force refresh and do not check cache + Returns: (dict) Metadata dictionary from name claim, None if the name is not resolvable @@ -1367,6 +1406,7 @@ class Daemon(AuthJSONRPCServer): else: defer.returnValue(metadata) + @AuthJSONRPCServer.deprecated('claim_show') def jsonrpc_get_claim_info(self, **kwargs): """ DEPRECATED. Use `claim_show` instead. @@ -1375,15 +1415,18 @@ class Daemon(AuthJSONRPCServer): @defer.inlineCallbacks def jsonrpc_claim_show(self, name=None, txid=None, nout=None, claim_id=None): - """ Resolve claim info from a LBRY name - Args: - 'name': (str) name to look up, do not include lbry:// prefix - 'txid'(optional): (str) if specified, look for claim with this txid - 'nout'(optional): (int) if specified, look for claim with this nout - 'claim_id'(optional): (str) if specified, look for claim with this claim_id + Usage: + claim_show [ | --txid=] [ | --nout=] + [ | --claim_id=] + + Options: + , --txid= : look for claim with this txid + , --nout= : look for claim with this nout + , --claim_id= : look for claim with this claim id + Returns: (dict) Dictionary contaning claim info, (bool) false if claim is not resolvable @@ -1414,13 +1457,17 @@ class Daemon(AuthJSONRPCServer): @AuthJSONRPCServer.auth_required @defer.inlineCallbacks + @AuthJSONRPCServer.flags(force='-f') def jsonrpc_resolve(self, uri, force=False): """ Resolve a LBRY URI - Args: - 'uri': (str) uri to download - 'force'(optional): (boolean) set to true to ignore cache and force refresh + Usage: + resolve [-f] + + Options: + -f : force refresh and ignore cache + Returns: None if nothing can be resolved, otherwise: If uri resolves to a channel or a claim in a channel: @@ -1502,11 +1549,15 @@ class Daemon(AuthJSONRPCServer): """ Download stream from a LBRY name. - Args: - 'uri': (str) lbry uri to download - 'file_name'(optional): (str) a user specified name for the downloaded file - 'timeout'(optional): (int) download timeout in number of seconds - 'download_directory'(optional): (str) path to directory where file will be saved + Usage: + get [ | --file_name=] [ | --timeout=] + [ | --download_directory=] + + Options: + : specified name for the downloaded file + : download timeout in number of seconds + : path to directory where file will be saved + Returns: (dict) Dictionary contaning information about the stream { @@ -1568,6 +1619,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(result) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('file_set_status') @AuthJSONRPCServer.auth_required def jsonrpc_stop_lbry_file(self, **kwargs): """ @@ -1575,6 +1627,7 @@ class Daemon(AuthJSONRPCServer): """ return self.jsonrpc_file_set_status(status='stop', **kwargs) + @AuthJSONRPCServer.deprecated('file_set_status') @AuthJSONRPCServer.auth_required def jsonrpc_start_lbry_file(self, **kwargs): """ @@ -1588,11 +1641,22 @@ class Daemon(AuthJSONRPCServer): """ Start or stop downloading a file - Args: - 'status': (str) "start" or "stop" - 'name' (optional): (str) start file by lbry name, - 'sd_hash' (optional): (str) start file by the hash in the name claim, - 'file_name' (optional): (str) start file by its name in the downloads folder, + Usage: + file_set_status [--sd_hash=] [--file_name=] + [--stream_hash=] [--claim_id=] + [--outpoint=] [--rowid=] + [--name=] + + Options: + --sd_hash= : set status of file with matching sd hash + --file_name= : set status of file with matching file name in the + downloads folder + --stream_hash= : set status of file with matching stream hash + --claim_id= : set status of file with matching claim id + --outpoint= : set status of file with matching claim outpoint + --rowid= : set status of file with matching row id + --name= : set status of file with matching associated name claim + Returns: (str) Confirmation message """ @@ -1618,21 +1682,28 @@ class Daemon(AuthJSONRPCServer): @AuthJSONRPCServer.auth_required @defer.inlineCallbacks + @AuthJSONRPCServer.flags(delete_target_file='-f', delete_all='-a') def jsonrpc_file_delete(self, delete_target_file=True, delete_all=False, **kwargs): """ - Delete a lbry file + Delete a LBRY file + + Usage: + file_delete [-a | -f] [--sd_hash=] [--file_name=] + [--stream_hash=] [--claim_id=] + [--outpoint=] [--rowid=] + [--name=] + + Options: + -a : delete file from downloads and delete stored blobs + -f : delete only from downloads, do not delete blobs + --sd_hash= : delete by file sd hash + --file_name : delete by file name in downloads folder + --stream_hash= : delete by file stream hash + --claim_id= : delete by file claim id + --outpoint= : delete by file claim outpoint + --rowid= : delete by file row id + --name= : delete by associated name claim of file - Args: - 'name' (optional): (str) delete file by lbry name, - 'sd_hash' (optional): (str) delete file by sd hash, - 'file_name' (optional): (str) delete file by the name in the downloads folder, - 'stream_hash' (optional): (str) delete file by stream hash, - 'claim_id' (optional): (str) delete file by claim ID, - 'outpoint' (optional): (str) delete file by claim outpoint, - 'rowid': (optional): (int) delete file by rowid in the file manager - 'delete_target_file' (optional): (bool) delete file from downloads folder, - defaults to true if false only the blobs and - db entries will be deleted Returns: (bool) true if deletion was successful """ @@ -1663,6 +1734,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(result) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('stream_cost_estimate') def jsonrpc_get_est_cost(self, **kwargs): """ DEPRECATED. Use `stream_cost_estimate` instead @@ -1674,10 +1746,13 @@ class Daemon(AuthJSONRPCServer): """ Get estimated cost for a lbry stream - Args: - 'name': (str) lbry name - 'size' (optional): (int) stream size, in bytes. if provided an sd blob - won't be downloaded. + Usage: + stream_cost_estimate [ | --size=] + + Options: + , --size= : stream size in bytes. if provided an sd blob won't be + downloaded. + Returns: (float) Estimated cost in lbry credits, returns None if uri is not resolveable @@ -1690,11 +1765,11 @@ class Daemon(AuthJSONRPCServer): @defer.inlineCallbacks def jsonrpc_channel_new(self, channel_name, amount): """ - Generate a publisher key and create a new certificate claim + Generate a publisher key and create a new '@' prefixed certificate claim - Args: - 'channel_name': (str) '@' prefixed name - 'amount': (float) amount to claim name + Usage: + channel_new ( | --channel_name=) + ( | --amount=) Returns: (dict) Dictionary containing result of the claim @@ -1732,6 +1807,9 @@ class Daemon(AuthJSONRPCServer): """ Get my channels + Usage: + channel_list_mine + Returns: (list) ClaimDict """ @@ -1756,38 +1834,48 @@ class Daemon(AuthJSONRPCServer): 'description' 'author' 'language' - 'license', + 'license' 'nsfw' Metadata can be set by either using the metadata argument or by setting individual arguments fee, title, description, author, language, license, license_url, thumbnail, preview, nsfw, or sources. Individual arguments will overwrite the fields specified in metadata argument. - Args: - 'name': (str) name to be claimed - 'bid': (float) amount of credits to commit in this claim, - 'metadata'(optional): (dict) Metadata to associate with the claim. - 'file_path'(optional): (str) path to file to be associated with name. If provided, - a lbry stream of this file will be used in 'sources'. - If no path is given but a metadata dict is provided, the source - from the given metadata will be used. - 'fee'(optional): (dict) Dictionary representing key fee to download content: - {currency_symbol: {'amount': float, 'address': str, optional}} - supported currencies: LBC, USD, BTC - If an address is not provided a new one will be automatically - generated. Default fee is zero. - 'title'(optional): (str) title of the file - 'description'(optional): (str) description of the file - 'author'(optional): (str) author of the file - 'language'(optional): (str), language code - 'license'(optional): (str) license for the file - 'license_url'(optional): (str) URL to license - 'thumbnail'(optional): (str) thumbnail URL for the file - 'preview'(optional): (str) preview URL for the file - 'nsfw'(optional): (bool) True if not safe for work - 'sources'(optional): (dict){'lbry_sd_hash':sd_hash} specifies sd hash of file - 'channel_name' (optional): (str) name of the publisher channel - 'channel_id' (optional): (str) claim id of the publisher channel + Usage: + publish ( | --name=) ( | --bid=) [--metadata=] + [--file_path=] [--fee=] [--title=] + [--description=<description>] [--author=<author>] [--language=<language>] + [--license=<license>] [--license_url=<license_url>] [--thumbnail=<thumbnail>] + [--preview=<preview>] [--nsfw=<nsfw>] [--sources=<sources>] + [--channel_name=<channel_name>] [--channel_id=<channel_id>] + + Options: + --metadata=<metadata> : ClaimDict to associate with the claim. + --file_path=<file_path> : path to file to be associated with name. If provided, + a lbry stream of this file will be used in 'sources'. + If no path is given but a metadata dict is provided, + the source from the given metadata will be used. + --fee=<fee> : Dictionary representing key fee to download content: + {currency_symbol: {'amount': float, + 'address': str, optional}} + supported currencies: LBC, USD, BTC + If an address is not provided a new one will be + automatically generated. Default fee is zero. + --title=<title> : title of the publication + --description=<description> : description of the publication + --author=<author> : author of the publication + --language=<language> : language of the publication + --license=<license> : publication license + --license_url=<license_url> : publication license url + --thumbnail=<thumbnail> : thumbnail url + --preview=<preview> : preview url + --nsfw=<nsfw> : title of the publication + --sources=<sources> : {'lbry_sd_hash':sd_hash} specifies sd hash of file + --channel_name=<channel_name> : name of the publisher channel name in the wallet + --channel_id=<channel_id> : claim id of the publisher channel, does not check + for channel claim being in the wallet. This allows + publishing to a channel where only the certificate + private key is in the wallet. Returns: (dict) Dictionary containing result of the claim @@ -1883,6 +1971,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(result) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('claim_abandon') @AuthJSONRPCServer.auth_required def jsonrpc_abandon_claim(self, **kwargs): """ @@ -1896,8 +1985,9 @@ class Daemon(AuthJSONRPCServer): """ Abandon a name and reclaim credits from the claim - Args: - 'claim_id': (str) claim_id of claim + Usage: + claim_abandon (<claim_id> | --claim_id=<claim_id>) + Return: (dict) Dictionary containing result of the claim { @@ -1919,18 +2009,22 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(err) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('claim_abandon') @AuthJSONRPCServer.auth_required def jsonrpc_abandon_name(self, **kwargs): """ DEPRECATED. Use `claim_abandon` instead """ + return self.jsonrpc_claim_abandon(**kwargs) + @AuthJSONRPCServer.deprecated('claim_support_new') @AuthJSONRPCServer.auth_required def jsonrpc_support_claim(self, **kwargs): """ - DEPRECATED. Use `claim_abandon` instead + DEPRECATED. Use `claim_support_new` instead """ + return self.jsonrpc_claim_new_support(**kwargs) @AuthJSONRPCServer.auth_required @@ -1939,10 +2033,10 @@ class Daemon(AuthJSONRPCServer): """ Support a name claim - Args: - 'name': (str) name - 'claim_id': (str) claim ID of claim to support - 'amount': (float) amount to support by + Usage: + claim_new_support (<name> | --name=<name>) (<claim_id> | --claim_id=<claim_id>) + (<amount> | --amount<amount>) + Return: (dict) Dictionary containing result of the claim { @@ -1956,7 +2050,7 @@ class Daemon(AuthJSONRPCServer): self.analytics_manager.send_claim_action('new_support') defer.returnValue(result) - # TODO: merge this into claim_list + @AuthJSONRPCServer.deprecated('claim_list_mine') @AuthJSONRPCServer.auth_required def jsonrpc_get_my_claim(self, name): """ @@ -1974,6 +2068,7 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda r: self._render_response(r)) return d + @AuthJSONRPCServer.deprecated('claim_list_mine') @AuthJSONRPCServer.auth_required def jsonrpc_get_name_claims(self): """ @@ -1987,8 +2082,9 @@ class Daemon(AuthJSONRPCServer): """ List my name claims - Args: - None + Usage: + claim_list_mine + Returns (list) List of name claims owned by user [ @@ -2016,12 +2112,14 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda claims: self._render_response(claims)) return d + @AuthJSONRPCServer.deprecated('claim_list') def jsonrpc_get_claims_for_name(self, **kwargs): """ DEPRECATED. Use `claim_list` instead. """ return self.jsonrpc_claim_list(**kwargs) + @AuthJSONRPCServer.deprecated('claim_list') def jsonrpc_get_claims_for_tx(self, **kwargs): """ DEPRECATED. Use `claim_list` instead. @@ -2033,8 +2131,9 @@ class Daemon(AuthJSONRPCServer): """ Get claims for a name - Args: - 'name': (str) search for claims on this name + Usage: + claim_list (<name> | --name=<name>) + Returns (dict) State of claims assigned for the name { @@ -2060,6 +2159,7 @@ class Daemon(AuthJSONRPCServer): claims = yield self.session.wallet.get_claims_for_name(name) defer.returnValue(claims) + @AuthJSONRPCServer.deprecated('transaction_list') @AuthJSONRPCServer.auth_required def jsonrpc_get_transaction_history(self): """ @@ -2072,8 +2172,9 @@ class Daemon(AuthJSONRPCServer): """ List transactions belonging to wallet - Args: - None + Usage: + transaction_list + Returns: (list) List of transactions """ @@ -2082,6 +2183,7 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda r: self._render_response(r)) return d + @AuthJSONRPCServer.deprecated('transaction_show') def jsonrpc_get_transaction(self, txid): """ DEPRECATED. Use `transaction_show` instead @@ -2092,8 +2194,9 @@ class Daemon(AuthJSONRPCServer): """ Get a decoded transaction from a txid - Args: - 'txid': (str) txid of transaction + Usage: + transaction_show (<txid> | --txid=<txid>) + Returns: (dict) JSON formatted transaction """ @@ -2102,6 +2205,7 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda r: self._render_response(r)) return d + @AuthJSONRPCServer.deprecated('wallet_is_address_mine') @AuthJSONRPCServer.auth_required def jsonrpc_address_is_mine(self, address): """ @@ -2114,8 +2218,9 @@ class Daemon(AuthJSONRPCServer): """ Checks if an address is associated with the current wallet. - Args: - 'address': (str) address to check in base58 + Usage: + wallet_is_address_mine (<address> | --address=<address>) + Returns: (bool) true, if address is associated with current wallet """ @@ -2124,10 +2229,11 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda is_mine: self._render_response(is_mine)) return d + @AuthJSONRPCServer.deprecated('wallet_public_key') @AuthJSONRPCServer.auth_required def jsonrpc_get_public_key_from_wallet(self, wallet): """ - DEPRECATED. Use `wallet_is_address_mine` instead + DEPRECATED. Use `wallet_public_key` instead """ return self.jsonrpc_wallet_public_key(wallet) @@ -2136,8 +2242,9 @@ class Daemon(AuthJSONRPCServer): """ Get public key from wallet address - Args: - 'address': (str) wallet address in base58 + Usage: + wallet_public_key (<address> | --address=<address>) + Returns: (list) list of public keys associated with address. Could contain more than one public key if multisig. @@ -2153,8 +2260,9 @@ class Daemon(AuthJSONRPCServer): """ List wallet addresses - Args: - None + Usage: + wallet_list + Returns: List of wallet addresses """ @@ -2163,6 +2271,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(addresses) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('wallet_new_address') @AuthJSONRPCServer.auth_required def jsonrpc_get_new_address(self): """ @@ -2175,8 +2284,9 @@ class Daemon(AuthJSONRPCServer): """ Generate a new wallet address - Args: - None + Usage: + wallet_new_address + Returns: (str) New wallet address in base58 """ @@ -2196,8 +2306,9 @@ class Daemon(AuthJSONRPCServer): Return an address containing no balance, will create a new address if there is none. - Args: - None + Usage: + wallet_unused_address + Returns: (str) Unused wallet address in base58 """ @@ -2215,11 +2326,11 @@ class Daemon(AuthJSONRPCServer): @defer.inlineCallbacks def jsonrpc_send_amount_to_address(self, amount, address): """ - Send credits to an address + Queue a payment of credits to an address + + Usage: + send_amount_to_address (<amount> | --amount=<amount>) (<address> | --address=<address>) - Args: - 'amount': (float) the amount to send - 'address': (str) the address of the recipient in base58 Returns: (bool) true if payment successfully scheduled """ @@ -2231,6 +2342,7 @@ class Daemon(AuthJSONRPCServer): self.analytics_manager.send_credits_sent() defer.returnValue(True) + @AuthJSONRPCServer.deprecated('block_show') def jsonrpc_get_block(self, **kwargs): """ DEPRECATED. Use `block_show` instead @@ -2241,8 +2353,13 @@ class Daemon(AuthJSONRPCServer): """ Get contents of a block - Args: - 'blockhash': (str) hash of the block to look up + Usage: + block_show (<blockhash> | --blockhash=<blockhash>) | (<height> | --height=<height>) + + Options: + <blockhash>, --blockhash=<blockhash> : hash of the block to look up + <height>, --height=<height> : height of the block to look up + Returns: (dict) Requested block """ @@ -2259,6 +2376,7 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda r: self._render_response(r)) return d + @AuthJSONRPCServer.deprecated('descriptor_get') @AuthJSONRPCServer.auth_required def jsonrpc_download_descriptor(self, **kwargs): """ @@ -2266,6 +2384,7 @@ class Daemon(AuthJSONRPCServer): """ return self.jsonrpc_descriptor_get(**kwargs) + @AuthJSONRPCServer.deprecated('blob_get') @AuthJSONRPCServer.auth_required def jsonrpc_descriptor_get(self, sd_hash, timeout=None, payment_rate_manager=None): """ @@ -2289,15 +2408,20 @@ class Daemon(AuthJSONRPCServer): """ Download and return a blob - Args: - 'blob_hash': (str) blob hash of blob to get - 'timeout'(optional): (int) timeout in number of seconds - 'encoding'(optional): (str) by default no attempt at decoding is made, - can be set to one of the following decoders: - 'json' - 'payment_rate_manager'(optional): if not given the default payment rate manager - will be used. supported alternative rate managers: - 'only-free' + Usage: + blob_get (<blob_hash> | --blob_hash=<blob_hash>) [--timeout=<timeout>] + [--encoding=<encoding>] [--payment_rate_manager=<payment_rate_manager>] + + Options: + --timeout=<timeout> : timeout in number of seconds + --encoding=<encoding> : by default no attempt at decoding is made, + can be set to one of the + following decoders: + 'json' + --payment_rate_manager=<payment_rate_manager> : if not given the default payment rate + manager will be used. + supported alternative rate managers: + 'only-free' Returns (str) Success/Fail message or (dict) decoded data @@ -2327,8 +2451,9 @@ class Daemon(AuthJSONRPCServer): """ Delete a blob - Args: - 'blob_hash': (str) hash of blob to get + Usage: + blob_delete (<blob_hash> | --blob_hash=<blob_hash) +t Returns: (str) Success/fail message """ @@ -2345,6 +2470,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response("Deleted %s" % blob_hash) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('peer_list') def jsonrpc_get_peers_for_hash(self, blob_hash): """ DEPRECATED. Use `peer_list` instead @@ -2355,9 +2481,12 @@ class Daemon(AuthJSONRPCServer): """ Get peers for blob hash - Args: - 'blob_hash': (str) blob hash - 'timeout'(optional): (int) peer search timeout in seconds + Usage: + peer_list (<blob_hash> | --blob_hash=<blob_hash>) [<timeout> | --timeout=<timeout>] + + Options: + <timeout>, --timeout=<timeout> : peer search timeout in seconds + Returns: (list) List of contacts """ @@ -2369,6 +2498,7 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda r: self._render_response(r)) return d + @AuthJSONRPCServer.deprecated('blob_announce_all') def jsonrpc_announce_all_blobs_to_dht(self): """ DEPRECATED. Use `blob_announce_all` instead. @@ -2379,8 +2509,9 @@ class Daemon(AuthJSONRPCServer): """ Announce all blobs to the DHT - Args: - None + Usage: + blob_announce_all + Returns: (str) Success/fail message """ @@ -2394,8 +2525,9 @@ class Daemon(AuthJSONRPCServer): """ Reflect a stream - Args: - 'sd_hash': (str) sd_hash of lbry file + Usage: + reflect (<sd_hash> | --sd_hash=<sd_hash>) + Returns: (bool) true if successful """ @@ -2406,6 +2538,7 @@ class Daemon(AuthJSONRPCServer): yield reupload.reflect_stream(lbry_file) defer.returnValue("Reflect success") + @AuthJSONRPCServer.deprecated('blob_list') def jsonrpc_get_blob_hashes(self): """ DEPRECATED. Use `blob_list` instead @@ -2461,6 +2594,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(blob_hashes_for_return) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('blob_reflect_all') def jsonrpc_reflect_all_blobs(self): """ DEPRECATED. Use `blob_reflect_all` instead @@ -2471,8 +2605,9 @@ class Daemon(AuthJSONRPCServer): """ Reflects all saved blobs - Args: - None + Usage: + blob_reflect_all + Returns: (bool) true if successful """ @@ -2487,10 +2622,13 @@ class Daemon(AuthJSONRPCServer): """ Get stream availability for lbry uri - Args: - 'uri' : (str) lbry uri - 'sd_timeout' (optional): (int) sd blob download timeout - 'peer_timeout' (optional): (int) how long to look for peers + Usage: + get_availability (<uri> | --uri=<uri>) [<sd_timeout> | --sd_timeout=<sd_timeout>] + [<peer_timeout> | --peer_timeout=<peer_timeout>] + + Options: + <sd_timeout>, --sd_timeout=<sd_timeout> : sd blob download timeout + <peer_timeout>, --peer_timeout=<peer_timeout> : how long to look for peers Returns: (float) Peers per blob / total blobs @@ -2554,6 +2692,7 @@ class Daemon(AuthJSONRPCServer): response = yield self._render_response(mean_availability) defer.returnValue(response) + @AuthJSONRPCServer.deprecated('status') def jsonrpc_get_start_notice(self): """ DEPRECATED. @@ -2576,6 +2715,28 @@ class Daemon(AuthJSONRPCServer): return d + + @defer.inlineCallbacks + def jsonrpc_cli_test_command(self, pos_arg, pos_args=[], pos_arg2=None, pos_arg3=None): + """ + This command is only for testing the CLI argument parsing + Usage: + cli_test_command (<pos_arg> | --pos_arg=<pos_arg>) + [<pos_args>...] [--pos_arg2=<pos_arg2>] + [--pos_arg3=<pos_arg3>] + + Options: + <pos_arg2>, --pos_arg2=<pos_arg2> : pos arg 2 + <pos_arg3>, --pos_arg3=<pos_arg3> : pos arg 3 + + Returns: + pos args + """ + out = (pos_arg, pos_args, pos_arg2, pos_arg3) + response = yield self._render_response(out) + defer.returnValue(response) + + class _ResolveNameHelper(object): def __init__(self, daemon, name, force_refresh): self.daemon = daemon diff --git a/lbrynet/lbrynet_daemon/DaemonCLI.py b/lbrynet/lbrynet_daemon/DaemonCLI.py index a242c55cd..afab796cf 100644 --- a/lbrynet/lbrynet_daemon/DaemonCLI.py +++ b/lbrynet/lbrynet_daemon/DaemonCLI.py @@ -1,23 +1,68 @@ -import argparse import json import os import sys import colorama - +from docopt import docopt +from collections import OrderedDict from lbrynet import conf from lbrynet.core import utils from lbrynet.lbrynet_daemon.auth.client import JSONRPCException, LBRYAPIClient -from lbrynet.lbrynet_daemon.Daemon import LOADING_WALLET_CODE +from lbrynet.lbrynet_daemon.Daemon import LOADING_WALLET_CODE, Daemon from jsonrpc.common import RPCError from urllib2 import URLError, HTTPError from httplib import UNAUTHORIZED -def main(): - colorama.init() - parser = argparse.ArgumentParser(add_help=False) - _, arguments = parser.parse_known_args() +def remove_brackets(key): + if key.startswith("<") and key.endswith(">"): + return str(key[1:-1]) + return key + +def set_flag_vals(flag_names, parsed_args): + kwargs = OrderedDict() + for key, arg in parsed_args.iteritems(): + if arg is None: + continue + elif key.startswith("--"): + if remove_brackets(key[2:]) not in kwargs: + k = remove_brackets(key[2:]) + kwargs[k] = guess_type(arg) + elif key in flag_names: + if remove_brackets(flag_names[key]) not in kwargs: + kwargs[remove_brackets(flag_names[key])] = guess_type(arg) + elif remove_brackets(key) not in kwargs: + kwargs[remove_brackets(key)] = guess_type(arg) + return kwargs + + +def main(): + if len(sys.argv[1:]): + method, args = sys.argv[1], sys.argv[2:] + else: + print_help() + return + + if method in ['help', '--help', '-h']: + if len(args) == 1: + print_help_for_command(args[0]) + else: + print_help() + return + + if method not in Daemon.callable_methods: + print_error("\"%s\" is not a valid command." % method) + return + + fn = Daemon.callable_methods[method] + if hasattr(fn, "_flags"): + flag_names = fn._flags + else: + flag_names = {} + + parsed = docopt(fn.__doc__, args) + kwargs = set_flag_vals(flag_names, parsed) + colorama.init() conf.initialize_settings() api = LBRYAPIClient.get_client() @@ -44,94 +89,43 @@ def main(): print " Status: " + message return 1 - if len(arguments) < 1: - print_help(api) - return 1 - - method = arguments[0] - try: - params = parse_params(arguments[1:]) - except InvalidParameters as e: - print_error(e.message) - return 1 - # TODO: check if port is bound. Error if its not - if method in ['--help', '-h', 'help']: - if len(params) == 0: - print_help(api) - elif 'command' not in params: - print_error( - 'To get help on a specific command, use `{} help command=COMMAND_NAME`'.format( - os.path.basename(sys.argv[0])) - ) + try: + result = api.call(method, **kwargs) + if isinstance(result, basestring): + # printing the undumped string is prettier + print result else: - print_help_for_command(api, params['command']) + print utils.json_dumps_pretty(result) + except (RPCError, KeyError, JSONRPCException, HTTPError) as err: + error_data = None + if isinstance(err, HTTPError): + error_body = err.read() + try: + error_data = json.loads(error_body) + except ValueError: + print ( + "There was an error, and the response was not valid JSON.\n" + + "Raw JSONRPC response:\n" + error_body + ) + return 1 - elif method not in api.commands(): - print_error("'" + method + "' is not a valid command.") + print_error(error_data['error']['message'] + "\n", suggest_help=False) + else: + print_error("Something went wrong\n", suggest_help=False) - else: - try: - result = api.call(method, params) - if isinstance(result, basestring): - # printing the undumped string is prettier - print result - else: - print utils.json_dumps_pretty(result) - except (RPCError, KeyError, JSONRPCException, HTTPError) as err: - error_data = None - if isinstance(err, HTTPError): - error_body = err.read() - try: - error_data = json.loads(error_body) - except ValueError: - print ( - "There was an error, and the response was not valid JSON.\n" + - "Raw JSONRPC response:\n" + error_body - ) - return 1 + print_help_for_command(method) - print_error(error_data['error']['message'] + "\n", suggest_help=False) - else: - print_error("Something went wrong\n", suggest_help=False) - - print_help_for_command(api, method) - if 'data' in error_data['error'] and 'traceback' in error_data['error']['data']: - print "Here's the traceback for the error you encountered:" - print "\n".join(error_data['error']['data']['traceback']) - return 1 - - -def parse_params(params): - if len(params) > 1: - return get_params_from_kwargs(params) - elif len(params) == 1: - try: - return json.loads(params[0]) - except ValueError: - return get_params_from_kwargs(params) - else: - return {} - - -class InvalidParameters(Exception): - pass - - -def get_params_from_kwargs(params): - params_for_return = {} - for i in params: - try: - eq_pos = i.index('=') - except ValueError: - raise InvalidParameters('{} is not in <key>=<value> format'.format(i)) - k, v = i[:eq_pos], i[eq_pos + 1:] - params_for_return[k] = guess_type(v) - return params_for_return + if 'data' in error_data['error'] and 'traceback' in error_data['error']['data']: + print "Here's the traceback for the error you encountered:" + print "\n".join(error_data['error']['data']['traceback']) + return 1 def guess_type(x): + if not isinstance(x, (unicode, str)): + return x if x in ('true', 'True', 'TRUE'): return True if x in ('false', 'False', 'FALSE'): @@ -159,7 +153,8 @@ def print_error(message, suggest_help=True): print_help_suggestion() -def print_help(api): +def print_help(): + commands = Daemon.callable_methods.keys() print "\n".join([ "NAME", " lbrynet-cli - LBRY command line client.", @@ -170,20 +165,18 @@ def print_help(api): "EXAMPLES", " lbrynet-cli commands # list available commands", " lbrynet-cli status # get daemon status", - " lbrynet-cli resolve_name name=what # resolve a name", - " lbrynet-cli help command=resolve_name # get help for a command", + " lbrynet-cli resolve_name what # resolve a name", + " lbrynet-cli help resolve_name # get help for a command", "", "COMMANDS", - wrap_list_to_term_width(api.commands(), prefix=' ') + wrap_list_to_term_width(commands, prefix=' ') ]) -def print_help_for_command(api, command): - help_response = api.call('help', {'command': command}) - print "Help for %s method:" % command - message = help_response['help'] if 'help' in help_response else help_response - message = "\n".join([' ' + line for line in message.split("\n")]) - print message +def print_help_for_command(command): + fn = Daemon.callable_methods.get(command) + if fn: + print "Help for %s method:\n%s" % (command, fn.__doc__) def wrap_list_to_term_width(l, width=None, separator=', ', prefix=''): diff --git a/lbrynet/lbrynet_daemon/auth/server.py b/lbrynet/lbrynet_daemon/auth/server.py index 5f46af3a3..3d5751035 100644 --- a/lbrynet/lbrynet_daemon/auth/server.py +++ b/lbrynet/lbrynet_daemon/auth/server.py @@ -1,7 +1,7 @@ import logging import urlparse -import inspect import json +import inspect from decimal import Decimal from zope.interface import implements @@ -15,9 +15,9 @@ from traceback import format_exc from lbrynet import conf from lbrynet.core.Error import InvalidAuthenticationToken from lbrynet.core import utils -from lbrynet.undecorated import undecorated from lbrynet.lbrynet_daemon.auth.util import APIKey, get_auth_message from lbrynet.lbrynet_daemon.auth.client import LBRY_SECRET +from lbrynet.undecorated import undecorated log = logging.getLogger(__name__) @@ -110,21 +110,30 @@ def jsonrpc_dumps_pretty(obj, **kwargs): separators=(',', ': '), **kwargs) + "\n" -class AuthorizedBase(object): - def __init__(self): - self.authorized_functions = [] - self.callable_methods = {} - self._call_lock = {} - self._queued_methods = [] +class JSONRPCServerType(type): + def __new__(mcs, name, bases, newattrs): + klass = type.__new__(mcs, name, bases, newattrs) + klass.callable_methods = {} + klass.deprecated_methods = {} + klass.authorized_functions = [] + klass.queued_methods = [] - for methodname in dir(self): + for methodname in dir(klass): if methodname.startswith("jsonrpc_"): - method = getattr(self, methodname) - self.callable_methods.update({methodname.split("jsonrpc_")[1]: method}) - if hasattr(method, '_auth_required'): - self.authorized_functions.append(methodname.split("jsonrpc_")[1]) - if hasattr(method, '_queued'): - self._queued_methods.append(methodname.split("jsonrpc_")[1]) + method = getattr(klass, methodname) + if not hasattr(method, '_deprecated'): + klass.callable_methods.update({methodname.split("jsonrpc_")[1]: method}) + if hasattr(method, '_auth_required'): + klass.authorized_functions.append(methodname.split("jsonrpc_")[1]) + if hasattr(method, '_queued'): + klass.queued_methods.append(methodname.split("jsonrpc_")[1]) + else: + klass.deprecated_methods.update({methodname.split("jsonrpc_")[1]: method}) + return klass + + +class AuthorizedBase(object): + __metaclass__ = JSONRPCServerType @staticmethod def auth_required(f): @@ -136,6 +145,23 @@ class AuthorizedBase(object): f._queued = True return f + @staticmethod + def deprecated(new_command=None): + def _deprecated_wrapper(f): + f._new_command = new_command + f._deprecated = True + return f + return _deprecated_wrapper + + @staticmethod + def flags(**kwargs): + def _flag_wrapper(f): + f._flags = {} + for k, v in kwargs.iteritems(): + f._flags[v] = k + return f + return _flag_wrapper + class AuthJSONRPCServer(AuthorizedBase): """Authorized JSONRPC server used as the base class for the LBRY API @@ -164,7 +190,7 @@ class AuthJSONRPCServer(AuthorizedBase): isLeaf = True def __init__(self, use_authentication=None): - AuthorizedBase.__init__(self) + self._call_lock = {} self._use_authentication = ( use_authentication if use_authentication is not None else conf.settings['use_auth_http'] ) @@ -263,7 +289,7 @@ class AuthJSONRPCServer(AuthorizedBase): id_ = None try: function_name = parsed.get('method') - is_queued = function_name in self._queued_methods + is_queued = function_name in self.queued_methods args = parsed.get('params', {}) id_ = parsed.get('id', None) token = parsed.pop('hmac', None) @@ -312,12 +338,16 @@ class AuthJSONRPCServer(AuthorizedBase): if args == EMPTY_PARAMS or args == []: args_dict = {} + _args, _kwargs = (), {} elif isinstance(args, dict): args_dict = args elif len(args) == 1 and isinstance(args[0], dict): # TODO: this is for backwards compatibility. Remove this once API and UI are updated # TODO: also delete EMPTY_PARAMS then args_dict = args[0] + _args, _kwargs = (), args + elif isinstance(args, list): + _args, _kwargs = args, {} else: # d = defer.maybeDeferred(function, *args) # if we want to support positional args too raise ValueError('Args must be a dict') @@ -337,7 +367,7 @@ class AuthJSONRPCServer(AuthorizedBase): if is_queued: d_lock = self._call_lock.get(function_name, False) if not d_lock: - d = defer.maybeDeferred(function, **args_dict) + d = defer.maybeDeferred(function, self, **args_dict) self._call_lock[function_name] = finished_deferred def _del_lock(*args): @@ -352,9 +382,9 @@ class AuthJSONRPCServer(AuthorizedBase): log.info("queued %s", function_name) d = d_lock d.addBoth(lambda _: log.info("running %s from queue", function_name)) - d.addCallback(lambda _: defer.maybeDeferred(function, **args_dict)) + d.addCallback(lambda _: defer.maybeDeferred(function, self, **args_dict)) else: - d = defer.maybeDeferred(function, **args_dict) + d = defer.maybeDeferred(function, self, **args_dict) # finished_deferred will callback when the request is finished # and errback if something went wrong. If the errback is @@ -374,28 +404,6 @@ class AuthJSONRPCServer(AuthorizedBase): (utils.now() - time_in).total_seconds())) return server.NOT_DONE_YET - @staticmethod - def _check_params(function, args_dict): - argspec = inspect.getargspec(undecorated(function)) - num_optional_params = 0 if argspec.defaults is None else len(argspec.defaults) - missing_required_params = [ - required_param - for required_param in argspec.args[1:-num_optional_params] - if required_param not in args_dict - ] - if len(missing_required_params): - return 'Missing required parameters', missing_required_params - - extraneous_params = [] if argspec.keywords is not None else [ - extra_param - for extra_param in args_dict - if extra_param not in argspec.args[1:] - ] - if len(extraneous_params): - return 'Extraneous parameters', extraneous_params - - return None, None - def _register_user_session(self, session_id): """ Add or update a HMAC secret for a session @@ -454,6 +462,16 @@ class AuthJSONRPCServer(AuthorizedBase): else: return server_port[0], 80 + def _check_deprecated(self, function_path): + if function_path in self.deprecated_methods: + deprecated_fn = self.deprecated_methods[function_path] + deprecated_function_path = function_path + new_function_path = deprecated_fn._new_command + log.warning("\"%s\" is deprecated, please update to use \"%s\"", + deprecated_function_path, new_function_path) + return new_function_path + return function_path + def _verify_method_is_callable(self, function_path): if function_path not in self.callable_methods: raise UnknownAPIMethodError(function_path) @@ -462,9 +480,32 @@ class AuthJSONRPCServer(AuthorizedBase): raise NotAllowedDuringStartupError(function_path) def _get_jsonrpc_method(self, function_path): + function_path = self._check_deprecated(function_path) self._verify_method_is_callable(function_path) return self.callable_methods.get(function_path) + @staticmethod + def _check_params(function, args_dict): + argspec = inspect.getargspec(undecorated(function)) + num_optional_params = 0 if argspec.defaults is None else len(argspec.defaults) + missing_required_params = [ + required_param + for required_param in argspec.args[1:-num_optional_params] + if required_param not in args_dict + ] + if len(missing_required_params): + return 'Missing required parameters', missing_required_params + + extraneous_params = [] if argspec.keywords is not None else [ + extra_param + for extra_param in args_dict + if extra_param not in argspec.args[1:] + ] + if len(extraneous_params): + return 'Extraneous parameters', extraneous_params + + return None, None + def _initialize_session(self, session_id): if not self.sessions.get(session_id, False): self._register_user_session(session_id) diff --git a/requirements.txt b/requirements.txt index ff67ac801..209d09f37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ Twisted==16.6.0 appdirs==1.4.3 argparse==1.2.1 +docopt==0.6.2 base58==0.2.2 git+https://github.com/lbryio/bumpversion.git#egg=bumpversion colorama==0.3.7 diff --git a/setup.py b/setup.py index 6958ea4ca..1654ab1f8 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ requires = [ 'seccure', 'txJSON-RPC', 'zope.interface', + 'docopt' ] console_scripts = [ diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py new file mode 100644 index 000000000..ef6fd1797 --- /dev/null +++ b/tests/integration/test_integration.py @@ -0,0 +1,102 @@ +""" +Start up the actual daemon and test some non blockchain commands here +""" + +from jsonrpc.proxy import JSONRPCProxy +import json +import subprocess +import unittest +import time +import os + +from urllib2 import URLError +from httplib import BadStatusLine +from socket import error + + +def shell_command(command): + FNULL = open(os.devnull, 'w') + p = subprocess.Popen(command,shell=False,stdout=FNULL,stderr=subprocess.STDOUT) + +def lbrynet_cli(commands): + cli_cmd=['lbrynet-cli'] + for cmd in commands: + cli_cmd.append(cmd) + p = subprocess.Popen(cli_cmd,shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + out,err = p.communicate() + return out,err + +lbrynet_rpc_port = '5279' +lbrynet = JSONRPCProxy.from_url("http://localhost:{}/lbryapi".format(lbrynet_rpc_port)) + + +class TestIntegration(unittest.TestCase): + @classmethod + def setUpClass(cls): + shell_command(['lbrynet-daemon']) + start_time = time.time() + STARTUP_TIMEOUT = 180 + while time.time() - start_time < STARTUP_TIMEOUT: + try: + status = lbrynet.status() + except (URLError,error,BadStatusLine) as e: + pass + else: + if status['is_running'] == True: + return + time.sleep(1) + raise Exception('lbrynet daemon failed to start') + + @classmethod + def tearDownClass(cls): + shell_command(['lbrynet-cli', 'daemon_stop']) + + def test_cli(self): + help_out,err = lbrynet_cli(['help']) + self.assertTrue(help_out) + + out,err = lbrynet_cli(['-h']) + self.assertEqual(out, help_out) + + out,err = lbrynet_cli(['--help']) + self.assertEqual(out, help_out) + + out,err = lbrynet_cli(['status']) + out = json.loads(out) + self.assertTrue(out['is_running']) + + + def test_cli_docopts(self): + out,err = lbrynet_cli(['cli_test_command']) + self.assertEqual('',out) + self.assertTrue('Usage' in err) + + out,err = lbrynet_cli(['cli_test_command','1','--not_a_arg=1']) + self.assertEqual('',out) + self.assertTrue('Usage' in err) + + out,err = lbrynet_cli(['cli_test_command','1']) + out = json.loads(out) + self.assertEqual([1,[],None,None], out) + + out,err = lbrynet_cli(['cli_test_command','1','--pos_arg2=1']) + out = json.loads(out) + self.assertEqual([1,[],1,None], out) + + + out,err = lbrynet_cli(['cli_test_command','1', '--pos_arg2=2','--pos_arg3=3']) + out = json.loads(out) + self.assertEqual([1,[],2,3], out) + + out,err = lbrynet_cli(['cli_test_command','1','2','3']) + out = json.loads(out) + # TODO: variable length arguments don't have guess_type() on them + self.assertEqual([1,['2','3'],None,None], out) + + + def test_status(self): + out = lbrynet.status() + self.assertTrue(out['is_running']) + +if __name__ =='__main__': + unittest.main() diff --git a/tests/unit/lbrynet_daemon/test_DaemonCLI.py b/tests/unit/lbrynet_daemon/test_DaemonCLI.py index ac5ae4190..fc1b2dcb8 100644 --- a/tests/unit/lbrynet_daemon/test_DaemonCLI.py +++ b/tests/unit/lbrynet_daemon/test_DaemonCLI.py @@ -16,23 +16,3 @@ class DaemonCLITests(unittest.TestCase): self.assertEqual(False, DaemonCLI.guess_type('false')) self.assertEqual(False, DaemonCLI.guess_type('False')) - def test_get_params(self): - test_params = [ - 'b64address=VdNmakxFORPSyfCprAD/eDDPk5TY9QYtSA==', - 'name=test', - 'amount=5.3', - 'n=5', - 'address=bY13xeAjLrsjP4KGETwStK2a9UgKgXVTXu', - 't=true', - 'f=False', - ] - test_r = { - 'b64address': 'VdNmakxFORPSyfCprAD/eDDPk5TY9QYtSA==', - 'name': 'test', - 'amount': 5.3, - 'n': 5, - 'address': 'bY13xeAjLrsjP4KGETwStK2a9UgKgXVTXu', - 't': True, - 'f': False, - } - self.assertDictEqual(test_r, DaemonCLI.get_params_from_kwargs(test_params))