Merge pull request #131 from lbryio/content_type
change content-type to content_type, refactor metadata validation
This commit is contained in:
commit
319e7ecc61
12 changed files with 466 additions and 171 deletions
|
@ -1,135 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
from lbrynet.conf import CURRENCIES
|
|
||||||
from lbrynet.core import utils
|
|
||||||
import logging
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
BITTREX_FEE = 0.0025
|
|
||||||
|
|
||||||
# Metadata version
|
|
||||||
SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
|
|
||||||
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
|
|
||||||
BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type', 'sources']
|
|
||||||
OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey']
|
|
||||||
|
|
||||||
MV001 = "0.0.1"
|
|
||||||
MV002 = "0.0.2"
|
|
||||||
CURRENT_METADATA_VERSION = MV002
|
|
||||||
|
|
||||||
METADATA_REVISIONS = {}
|
|
||||||
METADATA_REVISIONS[MV001] = {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS}
|
|
||||||
METADATA_REVISIONS[MV002] = {'required': ['nsfw', 'ver'], 'optional': ['license_url']}
|
|
||||||
|
|
||||||
# Fee version
|
|
||||||
BASE_FEE_FIELDS = ['amount', 'address']
|
|
||||||
|
|
||||||
FV001 = "0.0.1"
|
|
||||||
CURRENT_FEE_REVISION = FV001
|
|
||||||
|
|
||||||
FEE_REVISIONS = {}
|
|
||||||
FEE_REVISIONS[FV001] = {'required': BASE_FEE_FIELDS, 'optional': []}
|
|
||||||
|
|
||||||
|
|
||||||
def verify_name_characters(name):
|
|
||||||
for c in name:
|
|
||||||
assert c in NAME_ALLOWED_CHARSET, "Invalid character"
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class LBRYFeeValidator(dict):
|
|
||||||
def __init__(self, fee_dict):
|
|
||||||
dict.__init__(self)
|
|
||||||
assert len(fee_dict) == 1
|
|
||||||
self.fee_version = None
|
|
||||||
self.currency_symbol = None
|
|
||||||
|
|
||||||
fee_to_load = deepcopy(fee_dict)
|
|
||||||
|
|
||||||
for currency in fee_dict:
|
|
||||||
self._verify_fee(currency, fee_to_load)
|
|
||||||
|
|
||||||
self.amount = self._get_amount()
|
|
||||||
self.address = self[self.currency_symbol]['address']
|
|
||||||
|
|
||||||
def _get_amount(self):
|
|
||||||
amt = self[self.currency_symbol]['amount']
|
|
||||||
if isinstance(amt, float):
|
|
||||||
return amt
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
return float(amt)
|
|
||||||
except TypeError:
|
|
||||||
log.error('Failed to convert %s to float', amt)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _verify_fee(self, currency, f):
|
|
||||||
# str in case someone made a claim with a wierd fee
|
|
||||||
assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency)
|
|
||||||
self.currency_symbol = currency
|
|
||||||
self.update({currency: {}})
|
|
||||||
for version in FEE_REVISIONS:
|
|
||||||
self._load_revision(version, f)
|
|
||||||
if not f:
|
|
||||||
self.fee_version = version
|
|
||||||
break
|
|
||||||
assert f[self.currency_symbol] == {}, "Unknown fee keys: %s" % json.dumps(f.keys())
|
|
||||||
|
|
||||||
def _load_revision(self, version, f):
|
|
||||||
for k in FEE_REVISIONS[version]['required']:
|
|
||||||
assert k in f[self.currency_symbol], "Missing required fee field: %s" % k
|
|
||||||
self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)})
|
|
||||||
for k in FEE_REVISIONS[version]['optional']:
|
|
||||||
if k in f[self.currency_symbol]:
|
|
||||||
self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)})
|
|
||||||
|
|
||||||
|
|
||||||
class Metadata(dict):
|
|
||||||
@classmethod
|
|
||||||
def load_from_hex(cls, metadata):
|
|
||||||
return cls(json.loads(metadata.decode('hex')))
|
|
||||||
|
|
||||||
def __init__(self, metadata):
|
|
||||||
dict.__init__(self)
|
|
||||||
self.meta_version = None
|
|
||||||
metadata_to_load = deepcopy(metadata)
|
|
||||||
|
|
||||||
self._verify_sources(metadata_to_load)
|
|
||||||
self._verify_metadata(metadata_to_load)
|
|
||||||
|
|
||||||
def _load_revision(self, version, metadata):
|
|
||||||
for k in METADATA_REVISIONS[version]['required']:
|
|
||||||
assert k in metadata, "Missing required metadata field: %s" % k
|
|
||||||
self.update({k: metadata.pop(k)})
|
|
||||||
for k in METADATA_REVISIONS[version]['optional']:
|
|
||||||
if k == 'fee':
|
|
||||||
self._load_fee(metadata)
|
|
||||||
elif k in metadata:
|
|
||||||
self.update({k: metadata.pop(k)})
|
|
||||||
|
|
||||||
def _load_fee(self, metadata):
|
|
||||||
if 'fee' in metadata:
|
|
||||||
self['fee'] = LBRYFeeValidator(metadata.pop('fee'))
|
|
||||||
|
|
||||||
def _verify_sources(self, metadata):
|
|
||||||
assert "sources" in metadata, "No sources given"
|
|
||||||
for source in metadata['sources']:
|
|
||||||
assert source in SOURCE_TYPES, "Unknown source type"
|
|
||||||
|
|
||||||
def _verify_metadata(self, metadata):
|
|
||||||
for version in METADATA_REVISIONS:
|
|
||||||
self._load_revision(version, metadata)
|
|
||||||
if not metadata:
|
|
||||||
self.meta_version = version
|
|
||||||
if utils.version_is_greater_than(self.meta_version, "0.0.1"):
|
|
||||||
assert self.meta_version == self['ver'], "version mismatch"
|
|
||||||
break
|
|
||||||
assert metadata == {}, "Unknown metadata keys: %s" % json.dumps(metadata.keys())
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
return json.dumps(self).encode("hex")
|
|
||||||
|
|
||||||
def as_json(self):
|
|
||||||
return json.dumps(self)
|
|
|
@ -25,7 +25,7 @@ from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHand
|
||||||
from lbrynet.core.client.ClientRequest import ClientRequest
|
from lbrynet.core.client.ClientRequest import ClientRequest
|
||||||
from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError
|
from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError
|
||||||
from lbrynet.core.Error import InsufficientFundsError
|
from lbrynet.core.Error import InsufficientFundsError
|
||||||
from lbrynet.core.LBRYMetadata import Metadata
|
from lbrynet.metadata.LBRYMetadata import Metadata
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
alert = logging.getLogger("lbryalert." + __name__)
|
alert = logging.getLogger("lbryalert." + __name__)
|
||||||
|
@ -322,7 +322,7 @@ class LBRYWallet(object):
|
||||||
assert k in r, "getvalueforname response missing field %s" % k
|
assert k in r, "getvalueforname response missing field %s" % k
|
||||||
|
|
||||||
def _log_success(claim_id):
|
def _log_success(claim_id):
|
||||||
log.info("lbry://%s complies with %s, claimid: %s", name, metadata.meta_version, claim_id)
|
log.info("lbry://%s complies with %s, claimid: %s", name, metadata.version, claim_id)
|
||||||
return defer.succeed(None)
|
return defer.succeed(None)
|
||||||
|
|
||||||
if 'error' in result:
|
if 'error' in result:
|
||||||
|
@ -377,7 +377,7 @@ class LBRYWallet(object):
|
||||||
result = {}
|
result = {}
|
||||||
try:
|
try:
|
||||||
metadata = Metadata(json.loads(claim['value']))
|
metadata = Metadata(json.loads(claim['value']))
|
||||||
meta_ver = metadata.meta_version
|
meta_ver = metadata.version
|
||||||
sd_hash = metadata['sources']['lbry_sd_hash']
|
sd_hash = metadata['sources']['lbry_sd_hash']
|
||||||
d = self._save_name_metadata(name, txid, sd_hash)
|
d = self._save_name_metadata(name, txid, sd_hash)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
|
@ -1256,7 +1256,7 @@ class LBRYumWallet(LBRYWallet):
|
||||||
def _send_name_claim_update(self, name, claim_id, txid, value, amount):
|
def _send_name_claim_update(self, name, claim_id, txid, value, amount):
|
||||||
def send_claim_update(address):
|
def send_claim_update(address):
|
||||||
decoded_claim_id = claim_id.decode('hex')[::-1]
|
decoded_claim_id = claim_id.decode('hex')[::-1]
|
||||||
metadata = Metadata(value).as_json()
|
metadata = json.dumps(Metadata(value))
|
||||||
log.info("updateclaim %s %s %f %s %s '%s'", txid, address, amount, name, decoded_claim_id.encode('hex'), json.dumps(metadata))
|
log.info("updateclaim %s %s %f %s %s '%s'", txid, address, amount, name, decoded_claim_id.encode('hex'), json.dumps(metadata))
|
||||||
cmd = known_commands['updateclaim']
|
cmd = known_commands['updateclaim']
|
||||||
func = getattr(self.cmd_runner, cmd.name)
|
func = getattr(self.cmd_runner, cmd.name)
|
||||||
|
|
|
@ -40,10 +40,9 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream
|
||||||
from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher
|
from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher
|
||||||
from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager
|
from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager
|
||||||
from lbrynet.lbrynet_daemon.Lighthouse import LighthouseClient
|
from lbrynet.lbrynet_daemon.Lighthouse import LighthouseClient
|
||||||
from lbrynet.core.LBRYMetadata import Metadata
|
from lbrynet.metadata.LBRYMetadata import Metadata, verify_name_characters
|
||||||
from lbrynet.core import log_support
|
from lbrynet.core import log_support
|
||||||
from lbrynet.core import utils
|
from lbrynet.core import utils
|
||||||
from lbrynet.core.LBRYMetadata import verify_name_characters
|
|
||||||
from lbrynet.core.utils import generate_id
|
from lbrynet.core.utils import generate_id
|
||||||
from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings
|
from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings
|
||||||
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, \
|
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, \
|
||||||
|
|
|
@ -11,7 +11,7 @@ from twisted.internet.task import LoopingCall
|
||||||
from lbrynet.core.Error import InsufficientFundsError, KeyFeeAboveMaxAllowed
|
from lbrynet.core.Error import InsufficientFundsError, KeyFeeAboveMaxAllowed
|
||||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||||
from lbrynet.core.StreamDescriptor import download_sd_blob
|
from lbrynet.core.StreamDescriptor import download_sd_blob
|
||||||
from lbrynet.core.LBRYMetadata import LBRYFeeValidator
|
from lbrynet.metadata.LBRYFee import LBRYFeeValidator
|
||||||
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
|
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
|
||||||
from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME
|
from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import googlefinance
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
|
|
||||||
from lbrynet.core.LBRYMetadata import LBRYFeeValidator
|
from lbrynet.metadata.LBRYFee import LBRYFeeValidator
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from lbrynet.core.Error import InsufficientFundsError
|
||||||
from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file
|
from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file
|
||||||
from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob
|
from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob
|
||||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||||
from lbrynet.core.LBRYMetadata import Metadata, CURRENT_METADATA_VERSION
|
from lbrynet.metadata.LBRYMetadata import Metadata
|
||||||
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
|
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
|
||||||
from lbrynet import reflector
|
from lbrynet import reflector
|
||||||
from lbrynet.conf import LOG_FILE_NAME, REFLECTOR_SERVERS
|
from lbrynet.conf import LOG_FILE_NAME, REFLECTOR_SERVERS
|
||||||
|
@ -143,8 +143,8 @@ class Publisher(object):
|
||||||
|
|
||||||
def _update_metadata(self):
|
def _update_metadata(self):
|
||||||
filename = os.path.join(self.lbry_file.download_directory, self.lbry_file.file_name)
|
filename = os.path.join(self.lbry_file.download_directory, self.lbry_file.file_name)
|
||||||
self.metadata['content-type'] = get_content_type(filename)
|
self.metadata['content_type'] = get_content_type(filename)
|
||||||
self.metadata['ver'] = CURRENT_METADATA_VERSION
|
self.metadata['ver'] = Metadata.current_version
|
||||||
|
|
||||||
def _show_publish_error(self, err):
|
def _show_publish_error(self, err):
|
||||||
log.info(err.getTraceback())
|
log.info(err.getTraceback())
|
||||||
|
|
116
lbrynet/metadata/LBRYFee.py
Normal file
116
lbrynet/metadata/LBRYFee.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from lbrynet.metadata.Validator import Validator, skip_validate
|
||||||
|
from lbrynet.conf import CURRENCIES
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_supported_currency(fee):
|
||||||
|
assert len(fee) == 1
|
||||||
|
for c in fee:
|
||||||
|
assert c in CURRENCIES
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_amount(x):
|
||||||
|
return isinstance(x, float) and x > 0
|
||||||
|
|
||||||
|
|
||||||
|
class LBCFeeValidator(Validator):
|
||||||
|
FV001 = "0.0.1"
|
||||||
|
CURRENT_FEE_VERSION = FV001
|
||||||
|
|
||||||
|
FEE_REVISIONS = {}
|
||||||
|
|
||||||
|
FEE_REVISIONS[FV001] = [
|
||||||
|
(Validator.REQUIRE, 'amount', verify_amount),
|
||||||
|
(Validator.REQUIRE, 'address', skip_validate),
|
||||||
|
]
|
||||||
|
|
||||||
|
FEE_MIGRATIONS = []
|
||||||
|
|
||||||
|
current_version = CURRENT_FEE_VERSION
|
||||||
|
versions = FEE_REVISIONS
|
||||||
|
migrations = FEE_MIGRATIONS
|
||||||
|
|
||||||
|
def __init__(self, fee):
|
||||||
|
Validator.__init__(self, fee)
|
||||||
|
|
||||||
|
|
||||||
|
class BTCFeeValidator(Validator):
|
||||||
|
FV001 = "0.0.1"
|
||||||
|
CURRENT_FEE_VERSION = FV001
|
||||||
|
|
||||||
|
FEE_REVISIONS = {}
|
||||||
|
|
||||||
|
FEE_REVISIONS[FV001] = [
|
||||||
|
(Validator.REQUIRE, 'amount',verify_amount),
|
||||||
|
(Validator.REQUIRE, 'address', skip_validate),
|
||||||
|
]
|
||||||
|
|
||||||
|
FEE_MIGRATIONS = []
|
||||||
|
|
||||||
|
current_version = CURRENT_FEE_VERSION
|
||||||
|
versions = FEE_REVISIONS
|
||||||
|
migrations = FEE_MIGRATIONS
|
||||||
|
|
||||||
|
def __init__(self, fee):
|
||||||
|
Validator.__init__(self, fee)
|
||||||
|
|
||||||
|
|
||||||
|
class USDFeeValidator(Validator):
|
||||||
|
FV001 = "0.0.1"
|
||||||
|
CURRENT_FEE_VERSION = FV001
|
||||||
|
|
||||||
|
FEE_REVISIONS = {}
|
||||||
|
|
||||||
|
FEE_REVISIONS[FV001] = [
|
||||||
|
(Validator.REQUIRE, 'amount',verify_amount),
|
||||||
|
(Validator.REQUIRE, 'address', skip_validate),
|
||||||
|
]
|
||||||
|
|
||||||
|
FEE_MIGRATIONS = []
|
||||||
|
|
||||||
|
current_version = CURRENT_FEE_VERSION
|
||||||
|
versions = FEE_REVISIONS
|
||||||
|
migrations = FEE_MIGRATIONS
|
||||||
|
|
||||||
|
def __init__(self, fee):
|
||||||
|
Validator.__init__(self, fee)
|
||||||
|
|
||||||
|
|
||||||
|
class LBRYFeeValidator(Validator):
|
||||||
|
CV001 = "0.0.1"
|
||||||
|
CURRENT_CURRENCY_VERSION = CV001
|
||||||
|
|
||||||
|
CURRENCY_REVISIONS = {}
|
||||||
|
|
||||||
|
CURRENCY_REVISIONS[CV001] = [
|
||||||
|
(Validator.OPTIONAL, 'BTC', BTCFeeValidator.validate),
|
||||||
|
(Validator.OPTIONAL, 'USD', USDFeeValidator.validate),
|
||||||
|
(Validator.OPTIONAL, 'LBC', LBCFeeValidator.validate),
|
||||||
|
]
|
||||||
|
|
||||||
|
CURRENCY_MIGRATIONS = []
|
||||||
|
|
||||||
|
current_version = CURRENT_CURRENCY_VERSION
|
||||||
|
versions = CURRENCY_REVISIONS
|
||||||
|
migrations = CURRENCY_MIGRATIONS
|
||||||
|
|
||||||
|
def __init__(self, fee_dict):
|
||||||
|
Validator.__init__(self, fee_dict)
|
||||||
|
self.currency_symbol = self.keys()[0]
|
||||||
|
self.amount = self._get_amount()
|
||||||
|
self.address = self[self.currency_symbol]['address']
|
||||||
|
|
||||||
|
def _get_amount(self):
|
||||||
|
amt = self[self.currency_symbol]['amount']
|
||||||
|
if isinstance(amt, float):
|
||||||
|
return amt
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return float(amt)
|
||||||
|
except TypeError:
|
||||||
|
log.error('Failed to convert %s to float', amt)
|
||||||
|
raise
|
86
lbrynet/metadata/LBRYMetadata.py
Normal file
86
lbrynet/metadata/LBRYMetadata.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from lbrynet.metadata.Validator import Validator, skip_validate
|
||||||
|
from lbrynet.metadata.LBRYFee import LBRYFeeValidator, verify_supported_currency
|
||||||
|
from lbrynet.conf import SOURCE_TYPES
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
|
||||||
|
|
||||||
|
|
||||||
|
def verify_name_characters(name):
|
||||||
|
for c in name:
|
||||||
|
assert c in NAME_ALLOWED_CHARSET, "Invalid character"
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_sources(sources):
|
||||||
|
for source in sources:
|
||||||
|
assert source in SOURCE_TYPES, "Unknown source type: %s" % str(source)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Metadata(Validator):
|
||||||
|
MV001 = "0.0.1"
|
||||||
|
MV002 = "0.0.2"
|
||||||
|
MV003 = "0.0.3"
|
||||||
|
CURRENT_METADATA_VERSION = MV003
|
||||||
|
|
||||||
|
METADATA_REVISIONS = {}
|
||||||
|
|
||||||
|
METADATA_REVISIONS[MV001] = [
|
||||||
|
(Validator.REQUIRE, 'title', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'description', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'author', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'language', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'license', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'content-type', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'sources', validate_sources),
|
||||||
|
(Validator.OPTIONAL, 'thumbnail', skip_validate),
|
||||||
|
(Validator.OPTIONAL, 'preview', skip_validate),
|
||||||
|
(Validator.OPTIONAL, 'fee', verify_supported_currency),
|
||||||
|
(Validator.OPTIONAL, 'contact', skip_validate),
|
||||||
|
(Validator.OPTIONAL, 'pubkey', skip_validate),
|
||||||
|
]
|
||||||
|
|
||||||
|
METADATA_REVISIONS[MV002] = [
|
||||||
|
(Validator.REQUIRE, 'nsfw', skip_validate),
|
||||||
|
(Validator.REQUIRE, 'ver', skip_validate),
|
||||||
|
(Validator.OPTIONAL, 'license_url', skip_validate),
|
||||||
|
]
|
||||||
|
|
||||||
|
METADATA_REVISIONS[MV003] = [
|
||||||
|
(Validator.REQUIRE, 'content_type', skip_validate),
|
||||||
|
(Validator.SKIP, 'content-type'),
|
||||||
|
(Validator.OPTIONAL, 'sig', skip_validate),
|
||||||
|
(Validator.IF_KEY, 'sig', (Validator.REQUIRE, 'pubkey', skip_validate), Validator.DO_NOTHING),
|
||||||
|
(Validator.IF_KEY, 'pubkey', (Validator.REQUIRE, 'sig', skip_validate), Validator.DO_NOTHING),
|
||||||
|
]
|
||||||
|
|
||||||
|
MIGRATE_MV001_TO_MV002 = [
|
||||||
|
(Validator.IF_KEY, 'nsfw', Validator.DO_NOTHING, (Validator.LOAD, 'nsfw', False)),
|
||||||
|
(Validator.IF_KEY, 'ver', Validator.DO_NOTHING, (Validator.LOAD, 'ver', MV002)),
|
||||||
|
]
|
||||||
|
|
||||||
|
MIGRATE_MV002_TO_MV003 = [
|
||||||
|
(Validator.IF_KEY, 'content-type', (Validator.UPDATE, 'content-type', 'content_type'), Validator.DO_NOTHING),
|
||||||
|
(Validator.IF_VAL, 'ver', MV002, (Validator.LOAD, 'ver', MV003), Validator.DO_NOTHING),
|
||||||
|
]
|
||||||
|
|
||||||
|
METADATA_MIGRATIONS = [
|
||||||
|
MIGRATE_MV001_TO_MV002,
|
||||||
|
MIGRATE_MV002_TO_MV003,
|
||||||
|
]
|
||||||
|
|
||||||
|
current_version = CURRENT_METADATA_VERSION
|
||||||
|
versions = METADATA_REVISIONS
|
||||||
|
migrations = METADATA_MIGRATIONS
|
||||||
|
|
||||||
|
def __init__(self, metadata, process_now=True):
|
||||||
|
Validator.__init__(self, metadata, process_now)
|
||||||
|
self.meta_version = self.get('ver', Metadata.MV001)
|
||||||
|
self._load_fee()
|
||||||
|
|
||||||
|
def _load_fee(self):
|
||||||
|
if 'fee' in self:
|
||||||
|
self.update({'fee': LBRYFeeValidator(self['fee'])})
|
155
lbrynet/metadata/Validator.py
Normal file
155
lbrynet/metadata/Validator.py
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
from lbrynet.core.utils import version_is_greater_than
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def skip_validate(value):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def processor(cls):
|
||||||
|
for methodname in dir(cls):
|
||||||
|
method = getattr(cls, methodname)
|
||||||
|
if hasattr(method, 'cmd_name'):
|
||||||
|
cls.commands.update({method.cmd_name: methodname})
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
def cmd(cmd_name):
|
||||||
|
def wrapper(func):
|
||||||
|
func.cmd_name = cmd_name
|
||||||
|
return func
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@processor
|
||||||
|
class Validator(dict):
|
||||||
|
"""
|
||||||
|
Base class for validated dictionaries
|
||||||
|
"""
|
||||||
|
|
||||||
|
# override these
|
||||||
|
current_version = None
|
||||||
|
versions = {}
|
||||||
|
migrations = []
|
||||||
|
|
||||||
|
# built in commands
|
||||||
|
DO_NOTHING = "do_nothing"
|
||||||
|
UPDATE = "update_key"
|
||||||
|
IF_KEY = "if_key"
|
||||||
|
REQUIRE = "require"
|
||||||
|
SKIP = "skip"
|
||||||
|
OPTIONAL = "optional"
|
||||||
|
LOAD = "load"
|
||||||
|
IF_VAL = "if_val"
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_hex(cls, hex_val):
|
||||||
|
return cls(json.loads(hex_val.decode('hex')))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, value):
|
||||||
|
if cls(value):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __init__(self, value, process_now=False):
|
||||||
|
dict.__init__(self)
|
||||||
|
self._skip = []
|
||||||
|
value_to_load = deepcopy(value)
|
||||||
|
if process_now:
|
||||||
|
self.process(value_to_load)
|
||||||
|
self._verify_value(value_to_load)
|
||||||
|
self.version = self.get('ver', "0.0.1")
|
||||||
|
|
||||||
|
def process(self, value):
|
||||||
|
if self.migrations is not None:
|
||||||
|
self._migrate_value(value)
|
||||||
|
|
||||||
|
@cmd(DO_NOTHING)
|
||||||
|
def _do_nothing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@cmd(SKIP)
|
||||||
|
def _add_to_skipped(self, rx_value, key):
|
||||||
|
if key not in self._skip:
|
||||||
|
self._skip.append(key)
|
||||||
|
|
||||||
|
@cmd(UPDATE)
|
||||||
|
def _update(self, rx_value, old_key, new_key):
|
||||||
|
rx_value.update({new_key: rx_value.pop(old_key)})
|
||||||
|
|
||||||
|
@cmd(IF_KEY)
|
||||||
|
def _if_key(self, rx_value, key, if_true, if_else):
|
||||||
|
if key in rx_value:
|
||||||
|
return self._handle(if_true, rx_value)
|
||||||
|
return self._handle(if_else, rx_value)
|
||||||
|
|
||||||
|
@cmd(IF_VAL)
|
||||||
|
def _if_val(self, rx_value, key, val, if_true, if_else):
|
||||||
|
if key in rx_value:
|
||||||
|
if rx_value[key] == val:
|
||||||
|
return self._handle(if_true, rx_value)
|
||||||
|
return self._handle(if_else, rx_value)
|
||||||
|
|
||||||
|
@cmd(LOAD)
|
||||||
|
def _load(self, rx_value, key, value):
|
||||||
|
rx_value.update({key: value})
|
||||||
|
|
||||||
|
@cmd(REQUIRE)
|
||||||
|
def _require(self, rx_value, key, validator=None):
|
||||||
|
if key not in self._skip:
|
||||||
|
assert key in rx_value, "Key is missing: %s" % key
|
||||||
|
if isinstance(validator, type):
|
||||||
|
assert isinstance(rx_value[key], validator), "%s: %s isn't required %s" % (key, type(rx_value[key]), validator)
|
||||||
|
elif callable(validator):
|
||||||
|
assert validator(rx_value[key]), "Failed to validate %s" % key
|
||||||
|
self.update({key: rx_value.pop(key)})
|
||||||
|
|
||||||
|
@cmd(OPTIONAL)
|
||||||
|
def _optional(self, rx_value, key, validator=None):
|
||||||
|
if key in rx_value and key not in self._skip:
|
||||||
|
if isinstance(validator, type):
|
||||||
|
assert isinstance(rx_value[key], validator), "%s type %s isn't required %s" % (key, type(rx_value[key]), validator)
|
||||||
|
elif callable(validator):
|
||||||
|
assert validator(rx_value[key]), "Failed to validate %s" % key
|
||||||
|
self.update({key: rx_value.pop(key)})
|
||||||
|
|
||||||
|
def _handle(self, cmd_tpl, value):
|
||||||
|
if cmd_tpl == Validator.DO_NOTHING:
|
||||||
|
return
|
||||||
|
command = cmd_tpl[0]
|
||||||
|
f = getattr(self, self.commands[command])
|
||||||
|
if len(cmd_tpl) > 1:
|
||||||
|
args = (value,) + cmd_tpl[1:]
|
||||||
|
f(*args)
|
||||||
|
else:
|
||||||
|
f()
|
||||||
|
|
||||||
|
def _load_revision(self, version, value):
|
||||||
|
for k in self.versions[version]:
|
||||||
|
self._handle(k, value)
|
||||||
|
|
||||||
|
def _verify_value(self, value):
|
||||||
|
val_ver = value.get('ver', "0.0.1")
|
||||||
|
# verify version requirements in reverse order starting from the version asserted in the value
|
||||||
|
versions = sorted([v for v in self.versions if not version_is_greater_than(v, val_ver)], key=StrictVersion, reverse=True)
|
||||||
|
for version in versions:
|
||||||
|
self._load_revision(version, value)
|
||||||
|
assert value == {} or value.keys() == self._skip, "Unknown keys: %s" % json.dumps(value)
|
||||||
|
|
||||||
|
def _migrate_value(self, value):
|
||||||
|
for migration in self.migrations:
|
||||||
|
self._run_migration(migration, value)
|
||||||
|
|
||||||
|
def _run_migration(self, commands, value):
|
||||||
|
for cmd in commands:
|
||||||
|
self._handle(cmd, value)
|
||||||
|
|
0
lbrynet/metadata/__init__.py
Normal file
0
lbrynet/metadata/__init__.py
Normal file
|
@ -1,5 +1,5 @@
|
||||||
import mock
|
import mock
|
||||||
from lbrynet.core import LBRYMetadata
|
from lbrynet.metadata import LBRYMetadata
|
||||||
from lbrynet.lbrynet_daemon import LBRYExchangeRateManager
|
from lbrynet.lbrynet_daemon import LBRYExchangeRateManager
|
||||||
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
from lbrynet.core import LBRYMetadata
|
from lbrynet.metadata import LBRYMetadata
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
|
||||||
|
|
||||||
class MetadataTest(unittest.TestCase):
|
class MetadataTest(unittest.TestCase):
|
||||||
def test_assertion_if_source_is_missing(self):
|
def test_assertion_if_no_metadata(self):
|
||||||
metadata = {}
|
metadata = {}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(AssertionError):
|
||||||
LBRYMetadata.Metadata(metadata)
|
LBRYMetadata.Metadata(metadata)
|
||||||
|
|
||||||
|
def test_assertion_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(AssertionError):
|
||||||
|
LBRYMetadata.Metadata(metadata)
|
||||||
|
|
||||||
def test_metadata_works_without_fee(self):
|
def test_metadata_works_without_fee(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'license': 'Oscilloscope Laboratories',
|
||||||
|
@ -18,15 +31,14 @@ class MetadataTest(unittest.TestCase):
|
||||||
'sources': {
|
'sources': {
|
||||||
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
|
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
|
||||||
'content-type': 'audio/mpeg',
|
'content-type': 'audio/mpeg',
|
||||||
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
|
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
|
||||||
}
|
}
|
||||||
m = LBRYMetadata.Metadata(metadata)
|
m = LBRYMetadata.Metadata(metadata)
|
||||||
self.assertFalse('key' in m)
|
self.assertFalse('fee' in m)
|
||||||
|
|
||||||
def test_assertion_if_invalid_source(self):
|
def test_assertion_if_invalid_source(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'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.',
|
'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',
|
'language': 'en',
|
||||||
'title': "It's a Disaster",
|
'title': "It's a Disaster",
|
||||||
|
@ -34,7 +46,7 @@ class MetadataTest(unittest.TestCase):
|
||||||
'sources': {
|
'sources': {
|
||||||
'fake': 'source'},
|
'fake': 'source'},
|
||||||
'content-type': 'audio/mpeg',
|
'content-type': 'audio/mpeg',
|
||||||
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
|
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
|
||||||
}
|
}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(AssertionError):
|
||||||
LBRYMetadata.Metadata(metadata)
|
LBRYMetadata.Metadata(metadata)
|
||||||
|
@ -57,7 +69,6 @@ class MetadataTest(unittest.TestCase):
|
||||||
def test_version_is_001_if_all_fields_are_present(self):
|
def test_version_is_001_if_all_fields_are_present(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'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.',
|
'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',
|
'language': 'en',
|
||||||
'title': "It's a Disaster",
|
'title': "It's a Disaster",
|
||||||
|
@ -65,29 +76,26 @@ class MetadataTest(unittest.TestCase):
|
||||||
'sources': {
|
'sources': {
|
||||||
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
|
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
|
||||||
'content-type': 'audio/mpeg',
|
'content-type': 'audio/mpeg',
|
||||||
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
|
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
|
||||||
}
|
}
|
||||||
m = LBRYMetadata.Metadata(metadata)
|
m = LBRYMetadata.Metadata(metadata, process_now=False)
|
||||||
self.assertEquals('0.0.1', m.meta_version)
|
self.assertEquals('0.0.1', m.version)
|
||||||
|
|
||||||
def test_assertion_if_there_is_an_extra_field(self):
|
def test_assertion_if_there_is_an_extra_field(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'NASA',
|
'license': 'Oscilloscope Laboratories',
|
||||||
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
|
'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.',
|
||||||
'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',
|
'language': 'en',
|
||||||
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
|
'title': "It's a Disaster",
|
||||||
'title': 'Thermonuclear Art',
|
'author': 'Written and directed by Todd Berger',
|
||||||
'sources': {
|
'sources': {
|
||||||
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
|
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
|
||||||
'nsfw': False,
|
'content-type': 'audio/mpeg',
|
||||||
'content-type': 'video/mp4',
|
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg',
|
||||||
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg',
|
|
||||||
'MYSTERYFIELD': '?'
|
'MYSTERYFIELD': '?'
|
||||||
}
|
}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(AssertionError):
|
||||||
LBRYMetadata.Metadata(metadata)
|
LBRYMetadata.Metadata(metadata, process_now=False)
|
||||||
|
|
||||||
def test_version_is_002_if_all_fields_are_present(self):
|
def test_version_is_002_if_all_fields_are_present(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
|
@ -104,8 +112,26 @@ class MetadataTest(unittest.TestCase):
|
||||||
'content-type': 'video/mp4',
|
'content-type': 'video/mp4',
|
||||||
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
|
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
|
||||||
}
|
}
|
||||||
m = LBRYMetadata.Metadata(metadata)
|
m = LBRYMetadata.Metadata(metadata, process_now=False)
|
||||||
self.assertEquals('0.0.2', m.meta_version)
|
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 = LBRYMetadata.Metadata(metadata, process_now=False)
|
||||||
|
self.assertEquals('0.0.3', m.version)
|
||||||
|
|
||||||
def test_version_claimed_is_001_but_version_is_002(self):
|
def test_version_claimed_is_001_but_version_is_002(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
|
@ -123,7 +149,55 @@ class MetadataTest(unittest.TestCase):
|
||||||
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
|
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
|
||||||
}
|
}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(AssertionError):
|
||||||
LBRYMetadata.Metadata(metadata)
|
LBRYMetadata.Metadata(metadata, process_now=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(AssertionError):
|
||||||
|
LBRYMetadata.Metadata(metadata, process_now=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 = LBRYMetadata.Metadata(metadata, process_now=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 = LBRYMetadata.Metadata(metadata, process_now=True)
|
||||||
|
self.assertEquals('0.0.3', m.version)
|
Loading…
Reference in a new issue