Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
246 lines (189 sloc) 6.87 KB
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from __future__ import unicode_literals
import os
from functools import partial
from cached_property import cached_property
from .profile import Profile
from . import constraints
from . import exceptions
from . import helpers
from . import config
from . import types
# Module API
class Field(object):
"""Field representaion
# Arguments
descriptor (dict): schema field descriptor
missingValues (str[]): an array with string representing missing values
# Raises
TableSchemaException: raises any error that occurs during the process
"""
# Public
ERROR = config.ERROR
def __init__(self, descriptor, missing_values=config.DEFAULT_MISSING_VALUES,
# Internal
schema=None):
# Process descriptor
descriptor = helpers.expand_field_descriptor(descriptor)
# Set attributes
self.__descriptor = descriptor
self.__missing_values = missing_values
self.__schema = schema
self.__cast_function = self.__get_cast_function()
self.__check_functions = self.__get_check_functions()
@cached_property
def schema(self):
"""Returns a schema instance if the field belongs to some schema
# Returns
Schema: field's schema
"""
return self.__schema
@cached_property
def name(self):
"""Field name
# Returns
str: field name
"""
return self.__descriptor.get('name')
@cached_property
def type(self):
"""Field type
# Returns
str: field type
"""
return self.__descriptor.get('type')
@cached_property
def format(self):
"""Field format
# Returns
str: field format
"""
return self.__descriptor.get('format')
@cached_property
def missing_values(self):
"""Field's missing values
# Returns
str[]: missing values
"""
return self.__missing_values
@cached_property
def required(self):
"""Whether field is required
# Returns
bool: true if required
"""
return self.constraints.get('required', False)
@cached_property
def constraints(self):
"""Field constraints
# Returns
dict: dict of field constraints
"""
return self.__descriptor.get('constraints', {})
@cached_property
def descriptor(self):
"""Fields's descriptor
# Returns
dict: descriptor
"""
return self.__descriptor
@cached_property
def cast_function(self):
return self.__cast_function
@cached_property
def check_functions(self):
return self.__check_functions
def cast_value(self, value, constraints=True):
"""Cast given value according to the field type and format.
# Arguments
value (any): value to cast against field
constraints (boll/str[]): gets constraints configuration
- it could be set to true to disable constraint checks
- it could be an Array of constraints to check e.g. ['minimum', 'maximum']
# Raises
TableSchemaException: raises any error that occurs during the process
# Returns
any: returns cast value
"""
# Null value
if value in self.__missing_values:
# Whether missing_values should be preserved without being cast
if os.environ.get('TABLESCHEMA_PRESERVE_MISSING_VALUES'):
return value
value = None
# Cast value
cast_value = value
if value is not None:
cast_value = self.__cast_function(value)
if cast_value == config.ERROR:
raise exceptions.CastError((
'Field "{field.name}" can\'t cast value "{value}" '
'for type "{field.type}" with format "{field.format}"'
).format(field=self, value=value))
# Check value
if constraints:
for name, check in self.__check_functions.items():
if isinstance(constraints, list):
if name not in constraints:
continue
passed = check(cast_value)
if not passed:
raise exceptions.CastError((
'Field "{field.name}" has constraint "{name}" '
'which is not satisfied for value "{value}"'
).format(field=self, name=name, value=value))
return cast_value
def test_value(self, value, constraints=True):
"""Test whether value is compliant to the field.
# Arguments
value (any): value to cast against field
constraints (bool/str[]): constraints configuration
# Returns
bool: returns if value is compliant to the field
"""
try:
self.cast_value(value, constraints=constraints)
except exceptions.CastError:
return False
return True
# Private
def __get_cast_function(self):
options = {}
# Get cast options
for key in ['decimalChar', 'groupChar', 'bareNumber', 'trueValues', 'falseValues']:
value = self.descriptor.get(key)
if value is not None:
options[key] = value
try:
cast = getattr(types, 'cast_%s' % self.type)
except AttributeError:
message = 'Not supported field type: %s' % self.type
raise exceptions.TableSchemaException(message)
cast = partial(cast, self.format, **options)
return cast
def __get_check_functions(self):
checks = {}
cast = partial(self.cast_value, constraints=False)
whitelist = _get_field_constraints(self.type)
for name, constraint in self.constraints.items():
if name in whitelist:
# Cast enum constraint
if name in ['enum']:
constraint = list(map(cast, constraint))
# Cast maximum/minimum constraint
if name in ['maximum', 'minimum']:
constraint = cast(constraint)
check = getattr(constraints, 'check_%s' % name)
checks[name] = partial(check, constraint)
return checks
# Internal
def _get_field_constraints(type):
# Extract list of constraints for given type from jsonschema
jsonschema = Profile('table-schema').jsonschema
profile_types = jsonschema['properties']['fields']['items']['anyOf']
for profile_type in profile_types:
if type in profile_type['properties']['type']['enum']:
return profile_type['properties']['constraints']['properties'].keys()