Merge pull request #657 from lbryio/remove-old-metadata

remove lbrynet.metadata
This commit is contained in:
Jack Robison 2017-06-02 12:27:53 -04:00 committed by GitHub
commit 522cb9cb82
15 changed files with 129 additions and 766 deletions

View file

@ -14,6 +14,7 @@ at anytime.
### Changed ### Changed
* Do not catch base exception in API command resolve * Do not catch base exception in API command resolve
* Remove deprecated `lbrynet.metadata` and update what used it to instead use `lbryschema`
* *
### Fixed ### Fixed

View file

@ -45,6 +45,7 @@ class NegotiatedPaymentRateManager(object):
""" """
self.base = base self.base = base
self.min_blob_data_payment_rate = base.min_blob_data_payment_rate
self.points_paid = 0.0 self.points_paid = 0.0
self.blob_tracker = availability_tracker self.blob_tracker = availability_tracker
self.generous = generous if generous is not None else conf.settings['is_generous_host'] self.generous = generous if generous is not None else conf.settings['is_generous_host']

View file

@ -17,6 +17,7 @@ from twisted.python.failure import Failure
from lbryschema.claim import ClaimDict from lbryschema.claim import ClaimDict
from lbryschema.uri import parse_lbry_uri from lbryschema.uri import parse_lbry_uri
from lbryschema.error import URIParseError from lbryschema.error import URIParseError
from lbryschema.fee import Fee
# TODO: importing this when internet is disabled raises a socket.gaierror # TODO: importing this when internet is disabled raises a socket.gaierror
from lbryum.version import LBRYUM_VERSION from lbryum.version import LBRYUM_VERSION
@ -25,8 +26,6 @@ from lbrynet import conf, analytics
from lbrynet.conf import LBRYCRD_WALLET, LBRYUM_WALLET, PTC_WALLET from lbrynet.conf import LBRYCRD_WALLET, LBRYUM_WALLET, PTC_WALLET
from lbrynet.reflector import reupload from lbrynet.reflector import reupload
from lbrynet.reflector import ServerFactory as reflector_server_factory from lbrynet.reflector import ServerFactory as reflector_server_factory
from lbrynet.metadata.Fee import FeeValidator
from lbrynet.metadata.Metadata import verify_name_characters
from lbrynet.lbryfile.client.EncryptedFileDownloader import EncryptedFileSaverFactory from lbrynet.lbryfile.client.EncryptedFileDownloader import EncryptedFileSaverFactory
from lbrynet.lbryfile.client.EncryptedFileDownloader import EncryptedFileOpenerFactory from lbrynet.lbryfile.client.EncryptedFileDownloader import EncryptedFileOpenerFactory
@ -448,7 +447,7 @@ class Daemon(AuthJSONRPCServer):
isinstance(settings[key], setting_type) or isinstance(settings[key], setting_type) or
( (
key == "max_key_fee" and key == "max_key_fee" and
isinstance(FeeValidator(settings[key]).amount, setting_type) isinstance(Fee(settings[key]).amount, setting_type)
) )
) )
@ -686,7 +685,7 @@ class Daemon(AuthJSONRPCServer):
publisher = Publisher(self.session, self.lbry_file_manager, self.session.wallet, publisher = Publisher(self.session, self.lbry_file_manager, self.session.wallet,
certificate_id) certificate_id)
verify_name_characters(name) parse_lbry_uri(name)
if bid <= 0.0: if bid <= 0.0:
raise Exception("Invalid bid") raise Exception("Invalid bid")
if not file_path: if not file_path:
@ -823,7 +822,9 @@ class Daemon(AuthJSONRPCServer):
return d return d
def _add_key_fee_to_est_data_cost(self, fee, data_cost): def _add_key_fee_to_est_data_cost(self, fee, data_cost):
fee_amount = 0.0 if not fee else self.exchange_rate_manager.to_lbc(fee).amount fee_amount = 0.0 if not fee else self.exchange_rate_manager.convert_currency(fee.currency,
"LBC",
fee.amount)
return data_cost + fee_amount return data_cost + fee_amount
@defer.inlineCallbacks @defer.inlineCallbacks
@ -1596,7 +1597,7 @@ class Daemon(AuthJSONRPCServer):
name = resolved['name'] name = resolved['name']
claim_id = resolved['claim_id'] claim_id = resolved['claim_id']
stream_info = resolved['value'] stream_info = ClaimDict.load_dict(resolved['value'])
if claim_id in self.streams: if claim_id in self.streams:
log.info("Already waiting on lbry://%s to start downloading", name) log.info("Already waiting on lbry://%s to start downloading", name)
@ -1856,8 +1857,11 @@ class Daemon(AuthJSONRPCServer):
If no path is given but a metadata dict is provided, If no path is given but a metadata dict is provided,
the source from the given metadata will be used. the source from the given metadata will be used.
--fee=<fee> : Dictionary representing key fee to download content: --fee=<fee> : Dictionary representing key fee to download content:
{currency_symbol: {'amount': float, {
'address': str, optional}} 'currency': currency_symbol,
'amount': float,
'address': str, optional
}
supported currencies: LBC, USD, BTC supported currencies: LBC, USD, BTC
If an address is not provided a new one will be If an address is not provided a new one will be
automatically generated. Default fee is zero. automatically generated. Default fee is zero.
@ -1933,6 +1937,8 @@ class Daemon(AuthJSONRPCServer):
elif 'address' not in metadata['fee']: elif 'address' not in metadata['fee']:
address = yield self.session.wallet.get_unused_address() address = yield self.session.wallet.get_unused_address()
metadata['fee']['address'] = address metadata['fee']['address'] = address
if 'fee' in metadata and 'version' not in metadata['fee']:
metadata['fee']['version'] = '_0_0_1'
claim_dict = { claim_dict = {
'version': '_0_0_1', 'version': '_0_0_1',

View file

@ -3,10 +3,10 @@ import os
from twisted.internet import defer, threads from twisted.internet import defer, threads
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from lbrynet.core import utils from lbryschema.fee import Fee
from lbrynet.core.Error import InsufficientFundsError, KeyFeeAboveMaxAllowed from lbrynet.core.Error import InsufficientFundsError, KeyFeeAboveMaxAllowed
from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.core.StreamDescriptor import download_sd_blob
from lbrynet.metadata.Fee import FeeValidator
from lbrynet.lbryfilemanager.EncryptedFileDownloader import ManagedEncryptedFileDownloaderFactory from lbrynet.lbryfilemanager.EncryptedFileDownloader import ManagedEncryptedFileDownloaderFactory
from lbrynet import conf from lbrynet import conf
@ -94,25 +94,28 @@ class GetStream(object):
log.info("Downloading stream descriptor blob (%i seconds)", self.timeout_counter) log.info("Downloading stream descriptor blob (%i seconds)", self.timeout_counter)
def convert_max_fee(self): def convert_max_fee(self):
max_fee = FeeValidator(self.max_key_fee) currency, amount = self.max_key_fee['currency'], self.max_key_fee['amount']
if max_fee.currency_symbol == "LBC": return self.exchange_rate_manager.convert_currency(currency, "LBC", amount)
return max_fee.amount
return self.exchange_rate_manager.to_lbc(self.max_key_fee).amount
def set_status(self, status, name): def set_status(self, status, name):
log.info("Download lbry://%s status changed to %s" % (name, status)) log.info("Download lbry://%s status changed to %s" % (name, status))
self.code = next(s for s in STREAM_STAGES if s[0] == status) self.code = next(s for s in STREAM_STAGES if s[0] == status)
def check_fee(self, fee): def check_fee_and_convert(self, fee):
validated_fee = FeeValidator(fee) max_key_fee_amount = self.convert_max_fee()
max_key_fee = self.convert_max_fee() converted_fee_amount = self.exchange_rate_manager.convert_currency(fee.currency, "LBC",
converted_fee = self.exchange_rate_manager.to_lbc(validated_fee).amount fee.amount)
if converted_fee > self.wallet.get_balance(): if converted_fee_amount > self.wallet.get_balance():
raise InsufficientFundsError('Unable to pay the key fee of %s' % converted_fee) raise InsufficientFundsError('Unable to pay the key fee of %s' % converted_fee_amount)
if converted_fee > max_key_fee: if converted_fee_amount > max_key_fee_amount:
raise KeyFeeAboveMaxAllowed('Key fee %s above max allowed %s' % (converted_fee, raise KeyFeeAboveMaxAllowed('Key fee %s above max allowed %s' % (converted_fee_amount,
max_key_fee)) max_key_fee_amount))
return validated_fee converted_fee = {
'currency': 'LBC',
'amount': converted_fee_amount,
'address': fee.address
}
return Fee(converted_fee)
def get_downloader_factory(self, factories): def get_downloader_factory(self, factories):
for factory in factories: for factory in factories:
@ -143,8 +146,7 @@ class GetStream(object):
@defer.inlineCallbacks @defer.inlineCallbacks
def pay_key_fee(self, fee, name): def pay_key_fee(self, fee, name):
if fee is not None: if fee is not None:
fee_lbc = self.exchange_rate_manager.to_lbc(fee).amount yield self._pay_key_fee(fee.address, fee.amount, name)
yield self._pay_key_fee(fee.address, fee_lbc, name)
else: else:
defer.returnValue(None) defer.returnValue(None)
@ -165,12 +167,12 @@ class GetStream(object):
self._running = True self._running = True
self.set_status(INITIALIZING_CODE, name) self.set_status(INITIALIZING_CODE, name)
self.sd_hash = utils.get_sd_hash(stream_info) self.sd_hash = stream_info.source_hash
if 'fee' in stream_info['stream']['metadata']: if stream_info.has_fee:
try: try:
fee = yield threads.deferToThread(self.check_fee, fee = yield threads.deferToThread(self.check_fee_and_convert,
stream_info['stream']['metadata']['fee']) stream_info.source_fee)
except Exception as err: except Exception as err:
self._running = False self._running = False
self.finished_deferred.errback(err) self.finished_deferred.errback(err)

View file

@ -6,7 +6,6 @@ from twisted.internet import defer, threads
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from lbrynet import conf from lbrynet import conf
from lbrynet.metadata.Fee import FeeValidator
from lbrynet.core.Error import InvalidExchangeRateResponse from lbrynet.core.Error import InvalidExchangeRateResponse
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -19,6 +18,7 @@ COINBASE_FEE = 0.0 #add fee
class ExchangeRate(object): class ExchangeRate(object):
def __init__(self, market, spot, ts): def __init__(self, market, spot, ts):
assert int(time.time()) - ts < 600 assert int(time.time()) - ts < 600
assert spot > 0
self.currency_pair = (market[0:3], market[3:6]) self.currency_pair = (market[0:3], market[3:6])
self.spot = spot self.spot = spot
self.ts = ts self.ts = ts
@ -200,74 +200,5 @@ class ExchangeRateManager(object):
def fee_dict(self): def fee_dict(self):
return {market: market.rate.as_dict() for market in self.market_feeds} return {market: market.rate.as_dict() for market in self.market_feeds}
def to_lbc(self, fee):
if fee is None:
return None
if not isinstance(fee, FeeValidator):
fee_in = FeeValidator(fee)
else:
fee_in = fee
return FeeValidator({
'currency':fee_in.currency_symbol,
'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
'address': fee_in.address
})
class DummyBTCLBCFeed(MarketFeed):
def __init__(self):
MarketFeed.__init__(
self,
"BTCLBC",
"market name",
"derp.com",
None,
0.0
)
class DummyUSDBTCFeed(MarketFeed):
def __init__(self):
MarketFeed.__init__(
self,
"USDBTC",
"market name",
"derp.com",
None,
0.0
)
class DummyExchangeRateManager(object):
def __init__(self, rates):
self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()]
for feed in self.market_feeds:
feed.rate = ExchangeRate(
feed.market, rates[feed.market]['spot'], rates[feed.market]['ts'])
def convert_currency(self, from_currency, to_currency, amount):
log.debug("Converting %f %s to %s" % (amount, from_currency, to_currency))
for market in self.market_feeds:
if (market.rate_is_initialized and
market.rate.currency_pair == (from_currency, to_currency)):
return amount * market.rate.spot
for market in self.market_feeds:
if (market.rate_is_initialized and
market.rate.currency_pair[0] == from_currency):
return self.convert_currency(
market.rate.currency_pair[1], to_currency, amount * market.rate.spot)
def to_lbc(self, fee):
if fee is None:
return None
if not isinstance(fee, FeeValidator):
fee_in = FeeValidator(fee)
else:
fee_in = fee
return FeeValidator({
'currency':fee_in.currency_symbol,
'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
'address': fee_in.address
})

View file

@ -1,39 +0,0 @@
import logging
import fee_schemas
from lbrynet.metadata.StructuredDict import StructuredDict
log = logging.getLogger(__name__)
class FeeValidator(StructuredDict):
def __init__(self, fee):
self._versions = [
('0.0.1', fee_schemas.VER_001, None)
]
StructuredDict.__init__(self, fee, fee.get('ver', '0.0.1'))
self.currency_symbol = self['currency']
self.amount = self._get_amount()
self.address = self['address']
def _get_amount(self):
amt = self['amount']
try:
return float(amt)
except TypeError:
log.error('Failed to convert fee amount %s to float', amt)
raise
class LBCFeeValidator(StructuredDict):
pass
class BTCFeeValidator(StructuredDict):
pass
class USDFeeValidator(StructuredDict):
pass

View file

@ -1,44 +0,0 @@
import logging
from lbrynet.core import Error
from lbrynet.metadata.StructuredDict import StructuredDict
import metadata_schemas
log = logging.getLogger(__name__)
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
def verify_name_characters(name):
assert len(name) > 0, "Empty uri"
invalid_characters = {c for c in name if c not in NAME_ALLOWED_CHARSET}
if invalid_characters:
raise Error.InvalidName(name, invalid_characters)
return True
def migrate_001_to_002(metadata):
metadata['ver'] = '0.0.2'
metadata['nsfw'] = False
def migrate_002_to_003(metadata):
metadata['ver'] = '0.0.3'
if 'content-type' in metadata:
metadata['content_type'] = metadata['content-type']
del metadata['content-type']
class Metadata(StructuredDict):
current_version = '0.0.3'
_versions = [
('0.0.1', metadata_schemas.VER_001, None),
('0.0.2', metadata_schemas.VER_002, migrate_001_to_002),
('0.0.3', metadata_schemas.VER_003, migrate_002_to_003)
]
def __init__(self, metadata, migrate=True, target_version=None):
if not isinstance(metadata, dict):
raise TypeError("{} is not a dictionary".format(metadata))
starting_version = metadata.get('ver', '0.0.1')
StructuredDict.__init__(self, metadata, starting_version, migrate, target_version)

View file

@ -1,64 +0,0 @@
import jsonschema
import logging
from jsonschema import ValidationError
log = logging.getLogger(__name__)
class StructuredDict(dict):
"""
A dictionary that enforces a structure specified by a schema, and supports
migration between different versions of the schema.
"""
# To be specified in sub-classes, an array in the format
# [(version, schema, migration), ...]
_versions = []
# Used internally to allow schema lookups by version number
_schemas = {}
version = None
def __init__(self, value, starting_version, migrate=True, target_version=None):
dict.__init__(self, value)
self.version = starting_version
self._schemas = dict([(version, schema) for (version, schema, _) in self._versions])
self.validate(starting_version)
if migrate:
self.migrate(target_version)
def _upgrade_version_range(self, start_version, end_version):
after_starting_version = False
for version, schema, migration in self._versions:
if not after_starting_version:
if version == self.version:
after_starting_version = True
continue
yield version, schema, migration
if end_version and version == end_version:
break
def validate(self, version):
jsonschema.validate(self, self._schemas[version])
def migrate(self, target_version=None):
if target_version:
assert self._versions.index(target_version) > self.versions.index(self.version), \
"Current version is above target version"
for version, schema, migration in self._upgrade_version_range(self.version, target_version):
migration(self)
try:
self.validate(version)
except ValidationError as e:
raise ValidationError(
"Could not migrate to version %s due to validation error: %s" %
(version, e.message))
self.version = version

View file

@ -1,16 +0,0 @@
VER_001 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY fee schema 0.0.1',
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}

View file

@ -1,276 +0,0 @@
VER_001 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY metadata schema 0.0.1',
'definitions': {
'fee_info': {
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}
},
'type': 'object',
'properties': {
'ver': {
'type': 'string',
'default': '0.0.1'
},
'title': {
'type': 'string'
},
'description': {
'type': 'string'
},
'author': {
'type': 'string'
},
'language': {
'type': 'string'
},
'license': {
'type': 'string'
},
'content-type': {
'type': 'string'
},
'sources': {
'type': 'object',
'properties': {
'lbry_sd_hash': {
'type': 'string'
},
'btih': {
'type': 'string'
},
'url': {
'type': 'string'
}
},
'additionalProperties': False
},
'thumbnail': {
'type': 'string'
},
'preview': {
'type': 'string'
},
'fee': {
'properties': {
'LBC': {'$ref': '#/definitions/fee_info'},
'BTC': {'$ref': '#/definitions/fee_info'},
'USD': {'$ref': '#/definitions/fee_info'}
}
},
'contact': {
'type': 'number'
},
'pubkey': {
'type': 'string'
},
},
'required': [
'title', 'description', 'author', 'language', 'license', 'content-type', 'sources'],
'additionalProperties': False
}
VER_002 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY metadata schema 0.0.2',
'definitions': {
'fee_info': {
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}
},
'type': 'object',
'properties': {
'ver': {
'type': 'string',
'enum': ['0.0.2'],
},
'title': {
'type': 'string'
},
'description': {
'type': 'string'
},
'author': {
'type': 'string'
},
'language': {
'type': 'string'
},
'license': {
'type': 'string'
},
'content-type': {
'type': 'string'
},
'sources': {
'type': 'object',
'properties': {
'lbry_sd_hash': {
'type': 'string'
},
'btih': {
'type': 'string'
},
'url': {
'type': 'string'
}
},
'additionalProperties': False
},
'thumbnail': {
'type': 'string'
},
'preview': {
'type': 'string'
},
'fee': {
'properties': {
'LBC': {'$ref': '#/definitions/fee_info'},
'BTC': {'$ref': '#/definitions/fee_info'},
'USD': {'$ref': '#/definitions/fee_info'}
}
},
'contact': {
'type': 'number'
},
'pubkey': {
'type': 'string'
},
'license_url': {
'type': 'string'
},
'nsfw': {
'type': 'boolean',
'default': False
},
},
'required': [
'ver', 'title', 'description', 'author', 'language', 'license',
'content-type', 'sources', 'nsfw'
],
'additionalProperties': False
}
VER_003 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY metadata schema 0.0.3',
'definitions': {
'fee_info': {
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}
},
'type': 'object',
'properties': {
'ver': {
'type': 'string',
'enum': ['0.0.3'],
},
'title': {
'type': 'string'
},
'description': {
'type': 'string'
},
'author': {
'type': 'string'
},
'language': {
'type': 'string'
},
'license': {
'type': 'string'
},
'content_type': {
'type': 'string'
},
'sources': {
'type': 'object',
'properties': {
'lbry_sd_hash': {
'type': 'string'
},
'btih': {
'type': 'string'
},
'url': {
'type': 'string'
}
},
'additionalProperties': False
},
'thumbnail': {
'type': 'string'
},
'preview': {
'type': 'string'
},
'fee': {
'properties': {
'LBC': {'$ref': '#/definitions/fee_info'},
'BTC': {'$ref': '#/definitions/fee_info'},
'USD': {'$ref': '#/definitions/fee_info'}
}
},
'contact': {
'type': 'number'
},
'pubkey': {
'type': 'string'
},
'license_url': {
'type': 'string'
},
'nsfw': {
'type': 'boolean',
'default': False
},
'sig': {
'type': 'string'
}
},
'required': [
'ver', 'title', 'description', 'author', 'language', 'license',
'content_type', 'sources', 'nsfw'
],
'additionalProperties': False,
'dependencies': {
'pubkey': ['sig'],
'sig': ['pubkey']
}
}

View file

@ -6,6 +6,7 @@ from twisted.internet import defer
from lbrynet.core import PTCWallet from lbrynet.core import PTCWallet
from lbrynet.core import BlobAvailability from lbrynet.core import BlobAvailability
from lbrynet.lbrynet_daemon import ExchangeRateManager as ERM
from lbrynet import conf from lbrynet import conf
KB = 2**10 KB = 2**10
@ -40,6 +41,37 @@ class FakeNetwork(object):
return 1 return 1
class BTCLBCFeed(ERM.MarketFeed):
def __init__(self):
ERM.MarketFeed.__init__(
self,
"BTCLBC",
"market name",
"derp.com",
None,
0.0
)
class USDBTCFeed(ERM.MarketFeed):
def __init__(self):
ERM.MarketFeed.__init__(
self,
"USDBTC",
"market name",
"derp.com",
None,
0.0
)
class ExchangeRateManager(ERM.ExchangeRateManager):
def __init__(self, market_feeds, rates):
self.market_feeds = market_feeds
for feed in self.market_feeds:
feed.rate = ERM.ExchangeRate(
feed.market, rates[feed.market]['spot'], rates[feed.market]['ts'])
class Wallet(object): class Wallet(object):
def __init__(self): def __init__(self):
self.private_key = RSA.generate(1024) self.private_key = RSA.generate(1024)
@ -254,3 +286,6 @@ def mock_conf_settings(obj, settings={}):
conf.settings = original_settings conf.settings = original_settings
obj.addCleanup(_reset_settings) obj.addCleanup(_reset_settings)

View file

@ -1,218 +0,0 @@
from twisted.trial import unittest
from jsonschema import ValidationError
from lbrynet.core import Error
from lbrynet.metadata import Metadata
class MetadataTest(unittest.TestCase):
def test_name_error_if_blank(self):
with self.assertRaises(AssertionError):
Metadata.verify_name_characters("")
def test_name_error_if_contains_bad_chrs(self):
with self.assertRaises(Error.InvalidName):
Metadata.verify_name_characters("wu tang")
with self.assertRaises(Error.InvalidName):
Metadata.verify_name_characters("$wutang")
with self.assertRaises(Error.InvalidName):
Metadata.verify_name_characters("#wutang")
def test_validation_error_if_no_metadata(self):
metadata = {}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata)
def test_validation_error_if_source_is_missing(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata)
def test_metadata_works_without_fee(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
}
m = Metadata.Metadata(metadata)
self.assertFalse('fee' in m)
def test_validation_error_if_invalid_source(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'fake': 'source'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata)
def test_validation_error_if_missing_v001_field(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}},
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata)
def test_version_is_001_if_all_fields_are_present(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
}
m = Metadata.Metadata(metadata, migrate=False)
self.assertEquals('0.0.1', m.version)
def test_validation_error_if_there_is_an_extra_field(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
'MYSTERYFIELD': '?'
}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata, migrate=False)
def test_version_is_002_if_all_fields_are_present(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.2',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content-type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
m = Metadata.Metadata(metadata, migrate=False)
self.assertEquals('0.0.2', m.version)
def test_version_is_003_if_all_fields_are_present(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.3',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content_type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
m = Metadata.Metadata(metadata, migrate=False)
self.assertEquals('0.0.3', m.version)
def test_version_claimed_is_001_but_version_is_002(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.1',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content-type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata, migrate=False)
def test_version_claimed_is_002_but_version_is_003(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.2',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content_type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
with self.assertRaises(ValidationError):
Metadata.Metadata(metadata, migrate=False)
def test_version_001_ports_to_003(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
}
m = Metadata.Metadata(metadata, migrate=True)
self.assertEquals('0.0.3', m.version)
def test_version_002_ports_to_003(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.2',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content-type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
m = Metadata.Metadata(metadata, migrate=True)
self.assertEquals('0.0.3', m.version)

View file

@ -13,7 +13,8 @@ from lbrynet.lbrynet_daemon import ExchangeRateManager
from tests import util from tests import util
from tests.mocks import mock_conf_settings, FakeNetwork from tests.mocks import mock_conf_settings, FakeNetwork
from tests.mocks import BlobAvailabilityTracker as DummyBlobAvailabilityTracker from tests.mocks import BlobAvailabilityTracker as DummyBlobAvailabilityTracker
from tests.mocks import ExchangeRateManager as DummyExchangeRateManager
from tests.mocks import BTCLBCFeed, USDBTCFeed
def get_test_daemon(data_rate=None, generous=True, with_fee=False): def get_test_daemon(data_rate=None, generous=True, with_fee=False):
if data_rate is None: if data_rate is None:
@ -26,7 +27,8 @@ def get_test_daemon(data_rate=None, generous=True, with_fee=False):
daemon = LBRYDaemon(None, None) daemon = LBRYDaemon(None, None)
daemon.session = mock.Mock(spec=Session.Session) daemon.session = mock.Mock(spec=Session.Session)
daemon.session.wallet = mock.Mock(spec=Wallet.LBRYumWallet) daemon.session.wallet = mock.Mock(spec=Wallet.LBRYumWallet)
daemon.exchange_rate_manager = ExchangeRateManager.DummyExchangeRateManager(rates) market_feeds = [BTCLBCFeed(), USDBTCFeed()]
daemon.exchange_rate_manager = DummyExchangeRateManager(market_feeds, rates)
base_prm = PaymentRateManager.BasePaymentRateManager(rate=data_rate) base_prm = PaymentRateManager.BasePaymentRateManager(rate=data_rate)
prm = PaymentRateManager.NegotiatedPaymentRateManager(base_prm, DummyBlobAvailabilityTracker(), prm = PaymentRateManager.NegotiatedPaymentRateManager(base_prm, DummyBlobAvailabilityTracker(),
generous=generous) generous=generous)

View file

@ -1,11 +1,12 @@
from lbrynet.metadata import Fee from lbryschema.fee import Fee
from lbrynet.lbrynet_daemon import ExchangeRateManager from lbrynet.lbrynet_daemon import ExchangeRateManager
from lbrynet import conf
from lbrynet.core.Error import InvalidExchangeRateResponse from lbrynet.core.Error import InvalidExchangeRateResponse
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from tests import util from tests import util
from tests.mocks import ExchangeRateManager as DummyExchangeRateManager
from tests.mocks import BTCLBCFeed, USDBTCFeed
class FeeFormatTest(unittest.TestCase): class FeeFormatTest(unittest.TestCase):
@ -15,29 +16,70 @@ class FeeFormatTest(unittest.TestCase):
'amount': 10.0, 'amount': 10.0,
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
} }
fee = Fee.FeeValidator(fee_dict) fee = Fee(fee_dict)
self.assertEqual(10.0, fee['amount']) self.assertEqual(10.0, fee['amount'])
self.assertEqual('USD', fee['currency']) self.assertEqual('USD', fee['currency'])
def test_fee_zero(self):
fee_dict = {
'currency':'LBC',
'amount': 0.0,
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
}
fee = Fee(fee_dict)
self.assertEqual(0.0, fee['amount'])
self.assertEqual('LBC', fee['currency'])
class ExchangeRateTest(unittest.TestCase):
def setUp(self):
util.resetTime(self)
def test_invalid_rates(self):
with self.assertRaises(AssertionError):
ExchangeRateManager.ExchangeRate('USDBTC', 0, util.DEFAULT_ISO_TIME)
with self.assertRaises(AssertionError):
ExchangeRateManager.ExchangeRate('USDBTC', -1, util.DEFAULT_ISO_TIME)
class FeeTest(unittest.TestCase): class FeeTest(unittest.TestCase):
def setUp(self): def setUp(self):
util.resetTime(self) util.resetTime(self)
def test_fee_converts_to_lbc(self): def test_fee_converts_to_lbc(self):
fee_dict = { fee = Fee({
'currency':'USD', 'currency':'USD',
'amount': 10.0, 'amount': 10.0,
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
} })
rates = { rates = {
'BTCLBC': {'spot': 3.0, 'ts': util.DEFAULT_ISO_TIME + 1}, 'BTCLBC': {'spot': 3.0, 'ts': util.DEFAULT_ISO_TIME + 1},
'USDBTC': {'spot': 2.0, 'ts': util.DEFAULT_ISO_TIME + 2} 'USDBTC': {'spot': 2.0, 'ts': util.DEFAULT_ISO_TIME + 2}
} }
manager = ExchangeRateManager.DummyExchangeRateManager(rates)
result = manager.to_lbc(fee_dict).amount market_feeds = [BTCLBCFeed(), USDBTCFeed()]
manager = DummyExchangeRateManager(market_feeds,rates)
result = manager.convert_currency(fee.currency, "LBC", fee.amount)
self.assertEqual(60.0, result) self.assertEqual(60.0, result)
def test_missing_feed(self):
# test when a feed is missing for conversion
fee = Fee({
'currency':'USD',
'amount': 1.0,
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
})
rates = {
'BTCLBC': {'spot': 1.0, 'ts': util.DEFAULT_ISO_TIME + 1},
}
market_feeds = [BTCLBCFeed()]
manager = DummyExchangeRateManager(market_feeds,rates)
with self.assertRaises(Exception):
result = manager.convert_currency(fee.currency, "LBC", fee.amount)
class GoogleBTCFeedTest(unittest.TestCase): class GoogleBTCFeedTest(unittest.TestCase):
@defer.inlineCallbacks @defer.inlineCallbacks