This commit is contained in:
jessop 2019-11-13 17:50:35 -05:00 committed by Lex Berezhny
parent 246f055145
commit 25a91f89fd
13 changed files with 482 additions and 34 deletions

View file

@ -3271,6 +3271,337 @@ class Daemon(metaclass=JSONRPCServerType):
""" """
return self.get_est_cost_from_uri(uri) return self.get_est_cost_from_uri(uri)
COLLECTION_DOC = """
Create, list and abandon collections.
"""
@requires(WALLET_COMPONENT)
async def jsonrpc_collection_create(
self, name, bid, claims, allow_duplicate_name=False,
channel_id=None, channel_name=None, channel_account_id=None,
account_id=None, wallet_id=None, claim_address=None, funding_account_ids=None,
preview=False, blocking=False, **kwargs):
"""
Create a new collection ....
Usage:
collection_create (<name> | --name=<name>) (<bid> | --bid=<bid>)
(<claims>... | --claims=<claims>...)
[--allow_duplicate_name]
[--title=<title>] [--description=<description>]
[--tags=<tags>...] [--languages=<languages>...] [--locations=<locations>...]
[--thumbnail_url=<thumbnail_url>]
[--account_id=<account_id>] [--wallet_id=<wallet_id>]
[--claim_address=<claim_address>] [--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking]
Options:
--name=<name> : (str) name of the collection
--bid=<bid> : (decimal) amount to back the claim
--claims=<claims> : (list) claim ids to be included in the collection
--allow_duplicate_name : (bool) create new collection even if one already exists with
given name. default: false.
--title=<title> : (str) title of the collection
--description=<description> : (str) description of the collection
--clear_languages : (bool) clear existing languages (prior to adding new ones)
--tags=<tags> : (list) content tags
--clear_languages : (bool) clear existing languages (prior to adding new ones)
--languages=<languages> : (list) languages used by the collection,
using RFC 5646 format, eg:
for English `--languages=en`
for Spanish (Spain) `--languages=es-ES`
for Spanish (Mexican) `--languages=es-MX`
for Chinese (Simplified) `--languages=zh-Hans`
for Chinese (Traditional) `--languages=zh-Hant`
--locations=<locations> : (list) locations of the collection, consisting of 2 letter
`country` code and a `state`, `city` and a postal
`code` along with a `latitude` and `longitude`.
for JSON RPC: pass a dictionary with aforementioned
attributes as keys, eg:
...
"locations": [{'country': 'US', 'state': 'NH'}]
...
for command line: pass a colon delimited list
with values in the following order:
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
making sure to include colon for blank values, for
example to provide only the city:
... --locations="::Manchester"
with all values set:
... --locations="US:NH:Manchester:03101:42.990605:-71.460989"
optionally, you can just pass the "LATITUDE:LONGITUDE":
... --locations="42.990605:-71.460989"
finally, you can also pass JSON string of dictionary
on the command line as you would via JSON RPC
... --locations="{'country': 'US', 'state': 'NH'}"
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
--account_id=<account_id> : (str) account to use for holding the transaction
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the collection is sent to, if not specified
it will be determined automatically from the account
--preview : (bool) do not broadcast the transaction
--blocking : (bool) wait until transaction is in mempool
Returns: {Transaction}
"""
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
account = wallet.get_account_or_default(account_id)
funding_accounts = wallet.get_accounts_or_all(funding_account_ids)
self.valid_collection_name_or_error(name)
channel = await self.get_channel_or_none(wallet, channel_account_id, channel_id, channel_name, for_signing=True)
amount = self.get_dewies_or_error('bid', bid, positive_value=True)
claim_address = await self.get_receiving_address(claim_address, account)
existing_collections = await self.ledger.get_collections(accounts=wallet.accounts, claim_name=name)
if len(existing_collections) > 0:
if not allow_duplicate_name:
raise Exception(
f"You already have a collection under the name '{name}'. "
f"Use --allow-duplicate-name flag to override."
)
claim = Claim()
claim.collection.update(claims=claims, **kwargs) # maybe specify claims=[] # here
tx = await Transaction.claim_create(
name, claim, amount, claim_address, funding_accounts, funding_accounts[0], channel
)
new_txo = tx.outputs[0]
if channel:
new_txo.sign(channel)
await tx.sign(funding_accounts)
if not preview:
await self.broadcast_or_release(tx, blocking)
await self.storage.save_claims([self._old_get_temp_claim_info(
tx, new_txo, claim_address, claim, name, dewies_to_lbc(amount)
)])
# await self.analytics_manager.send_new_channel()
else:
await account.ledger.release_tx(tx)
return tx
@requires(WALLET_COMPONENT)
async def jsonrpc_collection_update(
self, claim_id, bid=None, claim=None, allow_duplicate_name=False,
channel_id=None, channel_name=None, channel_account_id=None, clear_channel=False,
account_id=None, wallet_id=None, claim_address=None, funding_account_ids=None,
preview=False, blocking=False, replace=False, **kwargs):
"""
Update an existing collection claim.
Usage:
collection_update (<claim_id> | --claim_id=<claim_id>) [--bid=<bid>]
[--claims=<claims>...] [--clear_claim_ids]
[--title=<title>] [--description=<description>]
[--tags=<tags>...] [--clear_tags]
[--languages=<languages>...] [--clear_languages]
[--locations=<locations>...] [--clear_locations]
[--thumbnail_url=<thumbnail_url>] [--cover_url=<cover_url>]
[--account_id=<account_id>] [--wallet_id=<wallet_id>]
[--claim_address=<claim_address>] [--new_signing_key]
[--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking] [--replace]
Options:
--claim_id=<claim_id> : (str) claim_id of the collection to update
--bid=<bid> : (decimal) amount to back the claim
--claims=<claim_ids> : (list) claim ids
--clear_claims : (bool) clear existing claim references (prior to adding new ones)
--title=<title> : (str) title of the collection
--description=<description> : (str) description of the collection
--tags=<tags> : (list) add content tags
--clear_tags : (bool) clear existing tags (prior to adding new ones)
--languages=<languages> : (list) languages used by the collection,
using RFC 5646 format, eg:
for English `--languages=en`
for Spanish (Spain) `--languages=es-ES`
for Spanish (Mexican) `--languages=es-MX`
for Chinese (Simplified) `--languages=zh-Hans`
for Chinese (Traditional) `--languages=zh-Hant`
--clear_languages : (bool) clear existing languages (prior to adding new ones)
--locations=<locations> : (list) locations of the collection, consisting of 2 letter
`country` code and a `state`, `city` and a postal
`code` along with a `latitude` and `longitude`.
for JSON RPC: pass a dictionary with aforementioned
attributes as keys, eg:
...
"locations": [{'country': 'US', 'state': 'NH'}]
...
for command line: pass a colon delimited list
with values in the following order:
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
making sure to include colon for blank values, for
example to provide only the city:
... --locations="::Manchester"
with all values set:
... --locations="US:NH:Manchester:03101:42.990605:-71.460989"
optionally, you can just pass the "LATITUDE:LONGITUDE":
... --locations="42.990605:-71.460989"
finally, you can also pass JSON string of dictionary
on the command line as you would via JSON RPC
... --locations="{'country': 'US', 'state': 'NH'}"
--clear_locations : (bool) clear existing locations (prior to adding new ones)
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
#--cover_url=<cover_url> : (str) url of cover image
--account_id=<account_id> : (str) account in which to look for collection (default: all)
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the collection is sent
--new_signing_key : (bool) generate a new signing key, will invalidate all previous publishes
--preview : (bool) do not broadcast the transaction
--blocking : (bool) wait until transaction is in mempool
--replace : (bool) instead of modifying specific values on
the collection, this will clear all existing values
and only save passed in values, useful for form
submissions where all values are always set
Returns: {Transaction}
"""
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
funding_accounts = wallet.get_accounts_or_all(funding_account_ids)
if account_id:
account = wallet.get_account_or_error(account_id)
accounts = [account]
else:
account = wallet.default_account
accounts = wallet.accounts
existing_collections = await self.ledger.get_collections(
wallet=wallet, accounts=accounts, claim_id=claim_id
)
if len(existing_collections) != 1:
account_ids = ', '.join(f"'{account.id}'" for account in accounts)
raise Exception(
f"Can't find the collection '{claim_id}' in account(s) {account_ids}."
)
# Here we might have a problem of replacing a stream with a collection
old_txo = existing_collections[0]
if not old_txo.claim.is_collection: # as we're only checking @ or not, this is not definitive
raise Exception(
f"A claim with id '{claim_id}' was found but it is not a stream or collection."
)
if bid is not None:
amount = self.get_dewies_or_error('bid', bid, positive_value=True)
else:
amount = old_txo.amount
if claim_address is not None:
self.valid_address_or_error(claim_address)
else:
claim_address = old_txo.get_address(account.ledger)
channel = None
if channel_id or channel_name:
channel = await self.get_channel_or_error(
wallet, channel_account_id, channel_id, channel_name, for_signing=True)
elif old_txo.claim.is_signed and not clear_channel and not replace:
channel = old_txo.channel
if replace:
claim = Claim()
claim.collection.message.source.CopyFrom(
old_txo.claim.collection.message.source
)
claim.collection.update(**kwargs)
else:
claim = Claim.from_bytes(old_txo.claim.to_bytes())
claim.collection.update(**kwargs)
tx = await Transaction.claim_update(
old_txo, claim, amount, claim_address, funding_accounts, funding_accounts[0], channel
)
new_txo = tx.outputs[0]
new_txo.script.generate()
if channel:
new_txo.sign(channel)
await tx.sign(funding_accounts)
if not preview:
await self.broadcast_or_release(tx, blocking)
await self.analytics_manager.send_claim_action('publish')
else:
await account.ledger.release_tx(tx)
return tx
@requires(WALLET_COMPONENT)
async def jsonrpc_collection_abandon(self, *args, **kwargs):
"""
Abandon one of my collection claims.
Usage:
collection_abandon [<claim_id> | --claim_id=<claim_id>]
[<txid> | --txid=<txid>] [<nout> | --nout=<nout>]
[--account_id=<account_id>] [--wallet_id=<wallet_id>]
[--preview] [--blocking]
Options:
--claim_id=<claim_id> : (str) claim_id of the claim to abandon
--txid=<txid> : (str) txid of the claim to abandon
--nout=<nout> : (int) nout of the claim to abandon
--account_id=<account_id> : (str) id of the account to use
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
--preview : (bool) do not broadcast the transaction
--blocking : (bool) wait until abandon is in mempool
Returns: {Transaction}
"""
return await self.jsonrpc_stream_abandon(*args, **kwargs)
@requires(WALLET_COMPONENT)
def jsonrpc_collection_list(self, resolve_claims=False, account_id=None, wallet_id=None, page=None, page_size=None):
"""
List my collection claims.
Usage:
collection_list [--resolve_claims] [<account_id> | --account_id=<account_id>] [--wallet_id=<wallet_id>]
[--page=<page>] [--page_size=<page_size>]
Options:
--resolve_claims : (bool) resolve every claim
--account_id=<account_id> : (str) id of the account to use
--wallet_id=<wallet_id> : (str) restrict results to specific wallet
--page=<page> : (int) page to return during paginating
--page_size=<page_size> : (int) number of items on page during pagination
Returns: {Paginated[Output]}
"""
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
if account_id:
account: LBCAccount = wallet.get_account_or_error(account_id)
collections = account.get_collections
collection_count = account.get_collection_count
else:
collections = partial(self.ledger.get_collections, wallet=wallet, accounts=wallet.accounts)
collection_count = partial(self.ledger.get_collection_count, wallet=wallet, accounts=wallet.accounts)
return paginate_rows(collections, collection_count, page, page_size, resolve=resolve_claims)
SUPPORT_DOC = """ SUPPORT_DOC = """
Create, list and abandon all types of supports. Create, list and abandon all types of supports.
""" """

View file

@ -9,6 +9,7 @@ async def jsonrpc_collection_create(
Usage: Usage:
collection_create (<name> | --name=<name>) (<bid> | --bid=<bid>) collection_create (<name> | --name=<name>) (<bid> | --bid=<bid>)
( --claims=<claimIds> )
[--allow_duplicate_name=<allow_duplicate_name>] [--allow_duplicate_name=<allow_duplicate_name>]
[--title=<title>] [--description=<description>] [--title=<title>] [--description=<description>]
[--tags=<tags>...] [--languages=<languages>...] [--locations=<locations>...] [--tags=<tags>...] [--languages=<languages>...] [--locations=<locations>...]
@ -20,12 +21,14 @@ async def jsonrpc_collection_create(
Options: Options:
--name=<name> : (str) name of the collection --name=<name> : (str) name of the collection
--bid=<bid> : (decimal) amount to back the claim --bid=<bid> : (decimal) amount to back the claim
--allow_duplicate_name=<allow_duplicate_name> : (bool) create new collection even if one already exists with --allow_duplicate_name=<allow_duplicate_name> : (bool) create new collection even if one already exists with
given name. default: false. given name. default: false.
--claims=<claims> : (list) claim ids --claims=<claimIds> : (list) claim ids
--title=<title> : (str) title of the publication --title=<title> : (str) title of the collection
--description=<description> : (str) description of the publication --description=<description> : (str) description of the collection
--clear_languages : (bool) clear existing languages (prior to adding new ones)
--tags=<tags> : (list) content tags --tags=<tags> : (list) content tags
--clear_languages : (bool) clear existing languages (prior to adding new ones)
--languages=<languages> : (list) languages used by the collection, --languages=<languages> : (list) languages used by the collection,
using RFC 5646 format, eg: using RFC 5646 format, eg:
for English `--languages=en` for English `--languages=en`
@ -65,7 +68,7 @@ async def jsonrpc_collection_create(
... --locations="{'country': 'US', 'state': 'NH'}" ... --locations="{'country': 'US', 'state': 'NH'}"
--thumbnail_url=<thumbnail_url>: (str) thumbnail url --thumbnail_url=<thumbnail_url>: (str) thumbnail url
# --cover_url=<cover_url> : (str) url of cover image # --cover_url=<cover_url> : (str) url of cover image
--account_id=<account_id> : (str) account to use for holding the transaction --account_id=<account_id> : (str) account to use for holding the transaction
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet --wallet_id=<wallet_id> : (str) restrict operation to specific wallet
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction --funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
@ -94,7 +97,7 @@ async def jsonrpc_collection_create(
) )
claim = Claim() claim = Claim()
claim.collection.update(**kwargs) #maybe specify claims=[] claim.collection.update(**kwargs) #maybe specify claims=[] # here
tx = await Transaction.claim_create( tx = await Transaction.claim_create(
name, claim, amount, claim_address, funding_accounts, funding_accounts[0], channel name, claim, amount, claim_address, funding_accounts, funding_accounts[0], channel
) )
@ -138,8 +141,10 @@ async def jsonrpc_collection_update(
Options: Options:
--claim_id=<claim_id> : (str) claim_id of the collection to update --claim_id=<claim_id> : (str) claim_id of the collection to update
--bid=<bid> : (decimal) amount to back the claim --bid=<bid> : (decimal) amount to back the claim
--title=<title> : (str) title of the publication --claims=<claim_ids> : (list) claim ids
--description=<description> : (str) description of the publication --clear_claims : (bool) clear existing claim references (prior to adding new ones)
--title=<title> : (str) title of the collection
--description=<description> : (str) description of the collection
--tags=<tags> : (list) add content tags --tags=<tags> : (list) add content tags
--clear_tags : (bool) clear existing tags (prior to adding new ones) --clear_tags : (bool) clear existing tags (prior to adding new ones)
--languages=<languages> : (list) languages used by the collection, --languages=<languages> : (list) languages used by the collection,

View file

@ -201,6 +201,8 @@ class JSONResponseEncoder(JSONEncoder):
output['short_url'] = output['meta'].pop('short_url') output['short_url'] = output['meta'].pop('short_url')
if 'canonical_url' in output['meta']: if 'canonical_url' in output['meta']:
output['canonical_url'] = output['meta'].pop('canonical_url') output['canonical_url'] = output['meta'].pop('canonical_url')
if txo.claims is not None:
output['claims'] = [self.encode_output(o) for o in txo.claims]
if txo.script.is_claim_name or txo.script.is_update_claim: if txo.script.is_claim_name or txo.script.is_update_claim:
try: try:
output['value'] = txo.claim output['value'] = txo.claim

View file

@ -347,10 +347,6 @@ class ClaimList(BaseMessageList[ClaimReference]):
__slots__ = () __slots__ = ()
item_class = ClaimReference item_class = ClaimReference
@property
def _message(self):
return self.message.claim_references
def append(self, value): def append(self, value):
self.add().claim_id = value self.add().claim_id = value

View file

@ -392,9 +392,10 @@ class Collection(BaseClaim):
def to_dict(self): def to_dict(self):
claim = super().to_dict() claim = super().to_dict()
if 'claim_references' in claim: if 'claim_references' in claim:
claim['claim_references'] = self.claims.ids claim['claims'] = self.claims.ids
del claim['claim_references']
return claim return claim
@property @property
def claims(self) -> ClaimList: def claims(self) -> ClaimList:
return ClaimList(self.message) return ClaimList(self.message.claim_references)

View file

@ -267,10 +267,25 @@ class CommandTestCase(IntegrationTestCase):
return await self.confirm_and_render( return await self.confirm_and_render(
self.daemon.jsonrpc_channel_abandon(*args, **kwargs), confirm self.daemon.jsonrpc_channel_abandon(*args, **kwargs), confirm
) )
# ClaimIDs = ....
async def collection_create(
self, name='firstcollection', bid='1.0', confirm=True, **kwargs):
return await self.confirm_and_render(
self.daemon.jsonrpc_collection_create(name, bid, **kwargs), confirm
)
# ClaimIDs = ....
async def collection_update(
self, claim_id, confirm=True, **kwargs):
return await self.confirm_and_render(
self.daemon.jsonrpc_collection_update(claim_id, **kwargs), confirm
)
# async def collection_create async def collection_abandon(self, *args, confirm=True, **kwargs):
# async def collection_update if 'blocking' not in kwargs:
# async def collection_abandon kwargs['blocking'] = False
return await self.confirm_and_render(
self.daemon.jsonrpc_stream_abandon(*args, **kwargs), confirm
)
async def support_create(self, claim_id, bid='1.0', confirm=True, **kwargs): async def support_create(self, claim_id, bid='1.0', confirm=True, **kwargs):
return await self.confirm_and_render( return await self.confirm_and_render(

View file

@ -5,7 +5,7 @@ from hashlib import sha256
from string import hexdigits from string import hexdigits
import ecdsa import ecdsa
from lbry.wallet.constants import TXO_TYPES from lbry.wallet.constants import CLAIM_TYPES
from torba.client.baseaccount import BaseAccount, HierarchicalDeterministic from torba.client.baseaccount import BaseAccount, HierarchicalDeterministic
@ -91,7 +91,7 @@ class Account(BaseAccount):
get_total_balance = partial(self.get_balance, confirmations=confirmations, include_claims=True) get_total_balance = partial(self.get_balance, confirmations=confirmations, include_claims=True)
total = await get_total_balance() total = await get_total_balance()
if reserved_subtotals: if reserved_subtotals:
claims_balance = await get_total_balance(txo_type__in=[TXO_TYPES['stream'], TXO_TYPES['channel']]) claims_balance = await get_total_balance(txo_type__in=CLAIM_TYPES)
for amount, spent, from_me, to_me, height in await self.get_support_summary(): for amount, spent, from_me, to_me, height in await self.get_support_summary():
if confirmations > 0 and not 0 < height <= self.ledger.headers.height - (confirmations - 1): if confirmations > 0 and not 0 < height <= self.ledger.headers.height - (confirmations - 1):
continue continue
@ -163,6 +163,12 @@ class Account(BaseAccount):
def get_channel_count(self, **constraints): def get_channel_count(self, **constraints):
return self.ledger.get_channel_count(wallet=self.wallet, accounts=[self], **constraints) return self.ledger.get_channel_count(wallet=self.wallet, accounts=[self], **constraints)
def get_collections(self, **constraints):
return self.ledger.get_collections(wallet=self.wallet, accounts=[self], **constraints)
def get_collection_count(self, **constraints):
return self.ledger.get_collection_count(wallet=self.wallet, accounts=[self], **constraints)
def get_supports(self, **constraints): def get_supports(self, **constraints):
return self.ledger.get_supports(wallet=self.wallet, accounts=[self], **constraints) return self.ledger.get_supports(wallet=self.wallet, accounts=[self], **constraints)

View file

@ -5,3 +5,9 @@ TXO_TYPES = {
"purchase": 4, "purchase": 4,
"collection": 5 "collection": 5
} }
CLAIM_TYPES = [
TXO_TYPES['stream'],
TXO_TYPES['channel'],
TXO_TYPES['collection'],
]

View file

@ -3,7 +3,7 @@ from typing import List
from torba.client.basedatabase import BaseDatabase from torba.client.basedatabase import BaseDatabase
from lbry.wallet.transaction import Output from lbry.wallet.transaction import Output
from lbry.wallet.constants import TXO_TYPES from lbry.wallet.constants import TXO_TYPES, CLAIM_TYPES
class WalletDatabase(BaseDatabase): class WalletDatabase(BaseDatabase):
@ -140,14 +140,7 @@ class WalletDatabase(BaseDatabase):
@staticmethod @staticmethod
def constrain_claims(constraints): def constrain_claims(constraints):
constraints['txo_type__in'] = [ constraints['txo_type__in'] = CLAIM_TYPES
TXO_TYPES['stream'], TXO_TYPES['channel']
]
def constrain_claims(constraints):
constraints['txo_type__in'] = [
TXO_TYPES['stream'], TXO_TYPES['channel']
]
async def get_claims(self, **constraints) -> List[Output]: async def get_claims(self, **constraints) -> List[Output]:
self.constrain_claims(constraints) self.constrain_claims(constraints)
@ -198,11 +191,11 @@ class WalletDatabase(BaseDatabase):
constraints['txo_type'] = TXO_TYPES['collection'] constraints['txo_type'] = TXO_TYPES['collection']
def get_collections(self, **constraints): def get_collections(self, **constraints):
self.constrain_supports(constraints) self.constrain_collections(constraints)
return self.get_utxos(**constraints) return self.get_utxos(**constraints)
def get_collection_count(self, **constraints): def get_collection_count(self, **constraints):
self.constrain_supports(constraints) self.constrain_collections(constraints)
return self.get_utxo_count(**constraints) return self.get_utxo_count(**constraints)
async def release_all_outputs(self, account): async def release_all_outputs(self, account):

View file

@ -186,10 +186,29 @@ class MainNetLedger(BaseLedger):
def get_channel_count(self, **constraints): def get_channel_count(self, **constraints):
return self.db.get_channel_count(**constraints) return self.db.get_channel_count(**constraints)
def get_collections(self, **constraints): async def get_collections(self, resolve=False, **constraints):
return self.db.get_collections(**constraints) collections = await self.db.get_collections(**constraints)
if resolve:
for collection in collections:
claim_ids = collection.claim.collection.claims.ids;
try:
resolve_results, _, _ = await self.claim_search([], claim_ids=collection.claim.collection.claims.ids)
except:
log.exception("Resolve failed while looking up collection claim ids:")
claims = []
for claim_id in claim_ids:
found = False
for txo in resolve_results:
if txo.claim_id == claim_id:
claims.append(txo)
found = True
break
if not found:
claims.append(None)
collection.claims = claims
return collections
def get_collection_count(self, **constraints): def get_collection_count(self, resolve=False, **constraints):
return self.db.get_collection_count(**constraints) return self.db.get_collection_count(**constraints)
def get_supports(self, **constraints): def get_supports(self, **constraints):

View file

@ -32,6 +32,7 @@ class Output(BaseOutput):
__slots__ = ( __slots__ = (
'channel', 'private_key', 'meta', 'channel', 'private_key', 'meta',
'purchase', 'purchased_claim', 'purchase_receipt', 'purchase', 'purchased_claim', 'purchase_receipt',
'claims',
) )
def __init__(self, *args, channel: Optional['Output'] = None, def __init__(self, *args, channel: Optional['Output'] = None,
@ -42,6 +43,7 @@ class Output(BaseOutput):
self.purchase: 'Output' = None # txo containing purchase metadata self.purchase: 'Output' = None # txo containing purchase metadata
self.purchased_claim: 'Output' = None # resolved claim pointed to by purchase self.purchased_claim: 'Output' = None # resolved claim pointed to by purchase
self.purchase_receipt: 'Output' = None # txo representing purchase receipt for this claim self.purchase_receipt: 'Output' = None # txo representing purchase receipt for this claim
self.claims: List['Output'] = None # resolved claims for collection
self.meta = {} self.meta = {}
def update_annotations(self, annotated): def update_annotations(self, annotated):

View file

@ -1257,3 +1257,55 @@ class SupportCommands(CommandTestCase):
self.assertFalse(txs2[0]['support_info'][0]['is_tip']) self.assertFalse(txs2[0]['support_info'][0]['is_tip'])
self.assertEqual(txs2[0]['value'], '0.0') self.assertEqual(txs2[0]['value'], '0.0')
self.assertEqual(txs2[0]['fee'], '-0.0001415') self.assertEqual(txs2[0]['fee'], '-0.0001415')
class CollectionCommands(CommandTestCase):
async def test_collections(self):
claim_ids = [
self.get_claim_id(tx) for tx in [
await self.stream_create('stream-one'),
await self.stream_create('stream-two')
]
]
claim_ids.append(claim_ids[0])
claim_ids.append('beef')
tx = await self.collection_create('radjingles', claims=claim_ids, title="boring title")
claim_id = self.get_claim_id(tx)
collections = await self.out(self.daemon.jsonrpc_collection_list())
self.assertEqual(collections['items'][0]['value']['title'], 'boring title')
self.assertEqual(collections['items'][0]['value']['claims'], claim_ids)
self.assertItemCount(collections, 1)
await self.assertBalance(self.account, '6.939679')
with self.assertRaisesRegex(Exception, "You already have a collection under the name 'radjingles'."):
await self.collection_create('radjingles', claims=claim_ids)
self.assertItemCount(await self.daemon.jsonrpc_collection_list(), 1)
await self.assertBalance(self.account, '6.939679')
collections = await self.out(self.daemon.jsonrpc_collection_list())
self.assertEqual(collections['items'][0]['value']['title'], 'boring title')
await self.collection_update(claim_id, title='fancy title')
collections = await self.out(self.daemon.jsonrpc_collection_list())
self.assertEqual(collections['items'][0]['value']['title'], 'fancy title')
self.assertEqual(collections['items'][0]['value']['claims'], claim_ids)
self.assertNotIn('claims', collections['items'][0])
await self.collection_create('radjingles', claims=claim_ids, allow_duplicate_name=True)
self.assertItemCount(await self.daemon.jsonrpc_collection_list(), 2)
await self.collection_abandon(claim_id)
self.assertItemCount(await self.daemon.jsonrpc_collection_list(), 1)
collections = await self.out(self.daemon.jsonrpc_collection_list(resolve_claims=True))
self.assertEqual(collections['items'][0]['claims'][0]['name'], 'stream-one')
self.assertEqual(collections['items'][0]['claims'][1]['name'], 'stream-two')
self.assertEqual(collections['items'][0]['claims'][2]['name'], 'stream-one')
self.assertIsNone(collections['items'][0]['claims'][3])
claims = await self.out(self.daemon.jsonrpc_claim_list())
self.assertEqual(claims['items'][0]['name'], 'radjingles')
self.assertEqual(claims['items'][1]['name'], 'stream-two')
self.assertEqual(claims['items'][2]['name'], 'stream-one')

View file

@ -1,7 +1,7 @@
from unittest import TestCase from unittest import TestCase
from decimal import Decimal from decimal import Decimal
from lbry.schema.claim import Claim, Stream from lbry.schema.claim import Claim, Stream, Collection
class TestClaimContainerAwareness(TestCase): class TestClaimContainerAwareness(TestCase):
@ -118,6 +118,26 @@ class TestTags(TestCase):
self.assertEqual(claim.channel.tags, ['anime']) self.assertEqual(claim.channel.tags, ['anime'])
class TestCollection(TestCase):
def test_collection(self):
collection = Collection()
collection.update(claims=['abc123', 'def123'])
self.assertListEqual(collection.claims.ids, ['abc123', 'def123'])
collection.update(claims=['abc123', 'bbb123'])
self.assertListEqual(collection.claims.ids, ['abc123', 'def123', 'abc123', 'bbb123'])
collection.update(clear_claims=True, claims=['bbb987', 'bb'])
self.assertListEqual(collection.claims.ids, ['bbb987', 'bb'])
self.assertEqual(collection.to_dict(), {'claims': ['bbb987', 'bb']})
collection.update(clear_claims=True)
self.assertListEqual(collection.claims.ids, [])
class TestLocations(TestCase): class TestLocations(TestCase):
def test_location_successful_parsing(self): def test_location_successful_parsing(self):