forked from LBRYCommunity/lbry-sdk
Merge pull request #187 from lbryio/new-metadata-system
Rewrite of migration and validation system using JSON Schema
This commit is contained in:
commit
018d78be6f
13 changed files with 421 additions and 350 deletions
|
@ -13,6 +13,7 @@ from twisted.python.failure import Failure
|
||||||
from twisted.enterprise import adbapi
|
from twisted.enterprise import adbapi
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
|
from jsonschema import ValidationError
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from lbryum import SimpleConfig, Network
|
from lbryum import SimpleConfig, Network
|
||||||
|
@ -338,7 +339,7 @@ class Wallet(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
metadata = Metadata(json.loads(result['value']))
|
metadata = Metadata(json.loads(result['value']))
|
||||||
except (ValueError, TypeError):
|
except ValidationError:
|
||||||
return Failure(InvalidStreamInfoError(name))
|
return Failure(InvalidStreamInfoError(name))
|
||||||
|
|
||||||
txid = result['txid']
|
txid = result['txid']
|
||||||
|
@ -421,7 +422,7 @@ class Wallet(object):
|
||||||
meta_ver = metadata.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 ValidationError:
|
||||||
metadata = claim['value']
|
metadata = claim['value']
|
||||||
meta_ver = "Non-compliant"
|
meta_ver = "Non-compliant"
|
||||||
d = defer.succeed(None)
|
d = defer.succeed(None)
|
||||||
|
|
|
@ -23,6 +23,7 @@ from twisted.internet.task import LoopingCall
|
||||||
from txjsonrpc import jsonrpclib
|
from txjsonrpc import jsonrpclib
|
||||||
from txjsonrpc.web import jsonrpc
|
from txjsonrpc.web import jsonrpc
|
||||||
from txjsonrpc.web.jsonrpc import Handler
|
from txjsonrpc.web.jsonrpc import Handler
|
||||||
|
from jsonschema import ValidationError
|
||||||
|
|
||||||
from lbrynet import __version__ as lbrynet_version
|
from lbrynet import __version__ as lbrynet_version
|
||||||
from lbryum.version import LBRYUM_VERSION as lbryum_version
|
from lbryum.version import LBRYUM_VERSION as lbryum_version
|
||||||
|
@ -2009,7 +2010,7 @@ class Daemon(jsonrpc.JSONRPC):
|
||||||
metadata = Metadata(p['metadata'])
|
metadata = Metadata(p['metadata'])
|
||||||
make_lbry_file = False
|
make_lbry_file = False
|
||||||
sd_hash = metadata['sources']['lbry_sd_hash']
|
sd_hash = metadata['sources']['lbry_sd_hash']
|
||||||
except AssertionError:
|
except ValidationError:
|
||||||
make_lbry_file = True
|
make_lbry_file = True
|
||||||
sd_hash = None
|
sd_hash = None
|
||||||
metadata = p['metadata']
|
metadata = p['metadata']
|
||||||
|
|
|
@ -160,4 +160,4 @@ class Publisher(object):
|
||||||
|
|
||||||
|
|
||||||
def get_content_type(filename):
|
def get_content_type(filename):
|
||||||
return mimetypes.guess_type(filename)[0]
|
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||||
|
|
|
@ -1,116 +1,39 @@
|
||||||
import logging
|
import logging
|
||||||
|
import fee_schemas
|
||||||
|
|
||||||
from lbrynet.metadata.Validator import Validator, skip_validate
|
from lbrynet.metadata.StructuredDict import StructuredDict
|
||||||
from lbrynet.conf import CURRENCIES
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def verify_supported_currency(fee):
|
class FeeValidator(StructuredDict):
|
||||||
assert len(fee) == 1
|
|
||||||
for c in fee:
|
|
||||||
assert c in CURRENCIES
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def verify_amount(x):
|
|
||||||
return isinstance(x, float) or isinstance(x, int) 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):
|
def __init__(self, fee):
|
||||||
Validator.__init__(self, fee)
|
self._versions = [
|
||||||
|
('0.0.1', fee_schemas.VER_001, None)
|
||||||
|
]
|
||||||
|
|
||||||
|
StructuredDict.__init__(self, fee, fee.get('ver', '0.0.1'))
|
||||||
|
|
||||||
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 FeeValidator(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.currency_symbol = self.keys()[0]
|
||||||
self.amount = self._get_amount()
|
self.amount = self._get_amount()
|
||||||
self.address = self[self.currency_symbol]['address']
|
self.address = self[self.currency_symbol]['address']
|
||||||
|
|
||||||
def _get_amount(self):
|
def _get_amount(self):
|
||||||
amt = self[self.currency_symbol]['amount']
|
amt = self[self.currency_symbol]['amount']
|
||||||
if isinstance(amt, float):
|
try:
|
||||||
return amt
|
return float(amt)
|
||||||
else:
|
except TypeError:
|
||||||
try:
|
log.error('Failed to convert fee amount %s to float', amt)
|
||||||
return float(amt)
|
raise
|
||||||
except TypeError:
|
|
||||||
log.error('Failed to convert %s to float', amt)
|
|
||||||
raise
|
class LBCFeeValidator(StructuredDict):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BTCFeeValidator(StructuredDict):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class USDFeeValidator(StructuredDict):
|
||||||
|
pass
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from lbrynet.metadata.Validator import Validator, skip_validate
|
from lbrynet.metadata.StructuredDict import StructuredDict
|
||||||
from lbrynet.metadata.Fee import FeeValidator, verify_supported_currency
|
import metadata_schemas
|
||||||
from lbrynet.conf import SOURCE_TYPES
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
|
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
|
||||||
|
@ -13,74 +12,27 @@ def verify_name_characters(name):
|
||||||
assert c in NAME_ALLOWED_CHARSET, "Invalid character"
|
assert c in NAME_ALLOWED_CHARSET, "Invalid character"
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def migrate_001_to_002(metadata):
|
||||||
|
metadata['ver'] = '0.0.2'
|
||||||
|
metadata['nsfw'] = False
|
||||||
|
|
||||||
def validate_sources(sources):
|
def migrate_002_to_003(metadata):
|
||||||
for source in sources:
|
metadata['ver'] = '0.0.3'
|
||||||
assert source in SOURCE_TYPES, "Unknown source type: %s" % str(source)
|
if 'content-type' in metadata:
|
||||||
return True
|
metadata['content_type'] = metadata['content-type']
|
||||||
|
del metadata['content-type']
|
||||||
|
|
||||||
|
|
||||||
class Metadata(Validator):
|
class Metadata(StructuredDict):
|
||||||
MV001 = "0.0.1"
|
current_version = '0.0.3'
|
||||||
MV002 = "0.0.2"
|
|
||||||
MV003 = "0.0.3"
|
|
||||||
CURRENT_METADATA_VERSION = MV003
|
|
||||||
|
|
||||||
METADATA_REVISIONS = {}
|
_versions = [
|
||||||
|
('0.0.1', metadata_schemas.VER_001, None),
|
||||||
METADATA_REVISIONS[MV001] = [
|
('0.0.2', metadata_schemas.VER_002, migrate_001_to_002),
|
||||||
(Validator.REQUIRE, 'title', skip_validate),
|
('0.0.3', metadata_schemas.VER_003, migrate_002_to_003)
|
||||||
(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] = [
|
def __init__(self, metadata, migrate=True, target_version=None):
|
||||||
(Validator.REQUIRE, 'nsfw', skip_validate),
|
starting_version = metadata.get('ver', '0.0.1')
|
||||||
(Validator.REQUIRE, 'ver', skip_validate),
|
|
||||||
(Validator.OPTIONAL, 'license_url', skip_validate),
|
|
||||||
]
|
|
||||||
|
|
||||||
METADATA_REVISIONS[MV003] = [
|
StructuredDict.__init__(self, metadata, starting_version, migrate, target_version)
|
||||||
(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': FeeValidator(self['fee'])})
|
|
62
lbrynet/metadata/StructuredDict.py
Normal file
62
lbrynet/metadata/StructuredDict.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
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
|
|
@ -1,155 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
16
lbrynet/metadata/fee_schemas.py
Normal file
16
lbrynet/metadata/fee_schemas.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
269
lbrynet/metadata/metadata_schemas.py
Normal file
269
lbrynet/metadata/metadata_schemas.py
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
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']
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ ecdsa==0.13
|
||||||
gmpy==1.17
|
gmpy==1.17
|
||||||
jsonrpc==1.2
|
jsonrpc==1.2
|
||||||
jsonrpclib==0.1.7
|
jsonrpclib==0.1.7
|
||||||
|
jsonschema==2.5.1
|
||||||
https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum
|
https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum
|
||||||
loggly-python-handler==1.0.0
|
loggly-python-handler==1.0.0
|
||||||
miniupnpc==1.9
|
miniupnpc==1.9
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -40,6 +40,7 @@ requires = [
|
||||||
'lbryum',
|
'lbryum',
|
||||||
'jsonrpc',
|
'jsonrpc',
|
||||||
'simplejson',
|
'simplejson',
|
||||||
|
'jsonschema',
|
||||||
'appdirs',
|
'appdirs',
|
||||||
'six==1.9.0',
|
'six==1.9.0',
|
||||||
'base58',
|
'base58',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import mock
|
import mock
|
||||||
from lbrynet.metadata import Metadata
|
from lbrynet.metadata import Fee
|
||||||
from lbrynet.lbrynet_daemon import ExchangeRateManager
|
from lbrynet.lbrynet_daemon import ExchangeRateManager
|
||||||
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
@ -13,7 +13,7 @@ class FeeFormatTest(unittest.TestCase):
|
||||||
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
|
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fee = Metadata.FeeValidator(fee_dict)
|
fee = Fee.FeeValidator(fee_dict)
|
||||||
self.assertEqual(10.0, fee['USD']['amount'])
|
self.assertEqual(10.0, fee['USD']['amount'])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from lbrynet.metadata import Metadata
|
from lbrynet.metadata import Metadata
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
from jsonschema import ValidationError
|
||||||
|
|
||||||
class MetadataTest(unittest.TestCase):
|
class MetadataTest(unittest.TestCase):
|
||||||
def test_assertion_if_no_metadata(self):
|
def test_validation_error_if_no_metadata(self):
|
||||||
metadata = {}
|
metadata = {}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(ValidationError):
|
||||||
Metadata.Metadata(metadata)
|
Metadata.Metadata(metadata)
|
||||||
|
|
||||||
def test_assertion_if_source_is_missing(self):
|
def test_validation_error_if_source_is_missing(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'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.',
|
'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.',
|
||||||
|
@ -18,7 +18,7 @@ class MetadataTest(unittest.TestCase):
|
||||||
'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(ValidationError):
|
||||||
Metadata.Metadata(metadata)
|
Metadata.Metadata(metadata)
|
||||||
|
|
||||||
def test_metadata_works_without_fee(self):
|
def test_metadata_works_without_fee(self):
|
||||||
|
@ -36,7 +36,7 @@ class MetadataTest(unittest.TestCase):
|
||||||
m = Metadata.Metadata(metadata)
|
m = Metadata.Metadata(metadata)
|
||||||
self.assertFalse('fee' in m)
|
self.assertFalse('fee' in m)
|
||||||
|
|
||||||
def test_assertion_if_invalid_source(self):
|
def test_validation_error_if_invalid_source(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'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.',
|
'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.',
|
||||||
|
@ -48,10 +48,10 @@ class MetadataTest(unittest.TestCase):
|
||||||
'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(ValidationError):
|
||||||
Metadata.Metadata(metadata)
|
Metadata.Metadata(metadata)
|
||||||
|
|
||||||
def test_assertion_if_missing_v001_field(self):
|
def test_validation_error_if_missing_v001_field(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'license': 'Oscilloscope Laboratories',
|
||||||
'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}},
|
'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}},
|
||||||
|
@ -63,7 +63,7 @@ class MetadataTest(unittest.TestCase):
|
||||||
'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(ValidationError):
|
||||||
Metadata.Metadata(metadata)
|
Metadata.Metadata(metadata)
|
||||||
|
|
||||||
def test_version_is_001_if_all_fields_are_present(self):
|
def test_version_is_001_if_all_fields_are_present(self):
|
||||||
|
@ -78,10 +78,10 @@ class MetadataTest(unittest.TestCase):
|
||||||
'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 = Metadata.Metadata(metadata, process_now=False)
|
m = Metadata.Metadata(metadata, migrate=False)
|
||||||
self.assertEquals('0.0.1', m.version)
|
self.assertEquals('0.0.1', m.version)
|
||||||
|
|
||||||
def test_assertion_if_there_is_an_extra_field(self):
|
def test_validation_error_if_there_is_an_extra_field(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
'license': 'Oscilloscope Laboratories',
|
'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.',
|
'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.',
|
||||||
|
@ -94,8 +94,8 @@ class MetadataTest(unittest.TestCase):
|
||||||
'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',
|
||||||
'MYSTERYFIELD': '?'
|
'MYSTERYFIELD': '?'
|
||||||
}
|
}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(ValidationError):
|
||||||
Metadata.Metadata(metadata, process_now=False)
|
Metadata.Metadata(metadata, migrate=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 = {
|
||||||
|
@ -112,7 +112,7 @@ 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 = Metadata.Metadata(metadata, process_now=False)
|
m = Metadata.Metadata(metadata, migrate=False)
|
||||||
self.assertEquals('0.0.2', m.version)
|
self.assertEquals('0.0.2', m.version)
|
||||||
|
|
||||||
def test_version_is_003_if_all_fields_are_present(self):
|
def test_version_is_003_if_all_fields_are_present(self):
|
||||||
|
@ -130,7 +130,7 @@ 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 = Metadata.Metadata(metadata, process_now=False)
|
m = Metadata.Metadata(metadata, migrate=False)
|
||||||
self.assertEquals('0.0.3', m.version)
|
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):
|
||||||
|
@ -148,8 +148,8 @@ 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'
|
||||||
}
|
}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(ValidationError):
|
||||||
Metadata.Metadata(metadata, process_now=False)
|
Metadata.Metadata(metadata, migrate=False)
|
||||||
|
|
||||||
def test_version_claimed_is_002_but_version_is_003(self):
|
def test_version_claimed_is_002_but_version_is_003(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
|
@ -166,8 +166,8 @@ 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'
|
||||||
}
|
}
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(ValidationError):
|
||||||
Metadata.Metadata(metadata, process_now=False)
|
Metadata.Metadata(metadata, migrate=False)
|
||||||
|
|
||||||
def test_version_001_ports_to_003(self):
|
def test_version_001_ports_to_003(self):
|
||||||
metadata = {
|
metadata = {
|
||||||
|
@ -181,7 +181,7 @@ class MetadataTest(unittest.TestCase):
|
||||||
'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 = Metadata.Metadata(metadata, process_now=True)
|
m = Metadata.Metadata(metadata, migrate=True)
|
||||||
self.assertEquals('0.0.3', m.version)
|
self.assertEquals('0.0.3', m.version)
|
||||||
|
|
||||||
def test_version_002_ports_to_003(self):
|
def test_version_002_ports_to_003(self):
|
||||||
|
@ -199,5 +199,5 @@ 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 = Metadata.Metadata(metadata, process_now=True)
|
m = Metadata.Metadata(metadata, migrate=True)
|
||||||
self.assertEquals('0.0.3', m.version)
|
self.assertEquals('0.0.3', m.version)
|
Loading…
Add table
Reference in a new issue