使用“Python描述符”的条件动态验证器

时间:2017-12-04 00:14:28

标签: python python-decorators python-descriptors

我想构建一个简单的“DataSource”类,其中包含名为“data_source_type”和“data_source_path”的属性。其中“data_source_type”是ENUM而“data_source_path”是一个字符串,根据“data_source_type”我想设置适当的验证,如“ValidFilePath”或“ValidHttpURL”到“data_source_path”。

我不想编写IF-ELSE并且有一个spaghetti数据源类,我想利用“Python描述符”或任何其他优雅的Python构造,它们会考虑SRP(单一责任原则)并支持函数式编程构造

data_source.py

1 import re
2 from enum import Enum
3 import os
4
5
6 class ValidFilePath(object):
7     def __set__(self, obj, val):
8         if not os.path.exists():
9             raise ValueError("Please enter a valid file path")
10             self.__url = val
11
12     def __get__(self, obj, objtype):
13         return self.__url
14
15
16 class ValidHttpURL(object):
17     def __set__(self, obj, val):
18         if (val is None or re.compile(
19                 r'^https?://'  # http:// or https://
20                 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'  # domain...
21                 r'localhost|'  # localhost...
22                 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
23                 r'(?::\d+)?'  # optional port
24                 r'(?:/?|[/?]\S+)$', re.IGNORECASE).search(val) is None):
25             raise ValueError("Please set an valid HTTP(S) URL")
26             self.__url = val
27
28     def __get__(self, obj, objtype):
29         return self.__url
30
31
32 class DataSourceType(Enum):
33     HTTP = 100,
34     LOCAL_FILE = 200,
35     HDFS_FILE = 300
36
37
38 class ValidDataSourceType(object):
39     def __set__(self, obj, val):
40         if val is None or not DataSourceType.__contains__(DataSourceType[val]):
41             raise ValueError("Please set a valid Data Source Type Enum, "
42                              " possible values are -> ", [e.name for e in DataSourceType])
43         self.__data_source_type = DataSourceType[val]
44
45     def __get__(self, obj, objtype):
46         return self.__data_source_type
47
48
49 class DataSource(object):
50     data_source_type = ValidDataSourceType()
51     data_source_path = ValidHttpURL()

截至第51行,我已经放了“ValidHttpURL”,我想根据“data_source_type”设置适当的验证描述符

预期行为

ds1 = DataSource()
ds1.data_source_type = 'HTTP'
ds1.data_source_path = 'http://www.google.com'
ds2 = DataSource()
ds2.data_source_type = 'LOCAL_FILE'
ds2.data_source_path = '/var/www/index.html'
print("All is well")

实际行为

ds1 = DataSource()
ds1.data_source_type = 'HTTP'
ds1.data_source_path = 'http://www.google.com'
ds2 = DataSource()
ds2.data_source_type = 'LOCAL_FILE'
ds2.data_source_path = '/var/www/index.html'

**ValueError: Please set an valid HTTP(S) URL**

***更新的答案****

  1 import os
  2 import re
  3 from enum import Enum
  4 from weakref import WeakKeyDictionary
  5
  6
  7 def valid_file_path(value):
  8     if not os.path.exists(value):
  9         raise ValueError(value, " is not present. Please make sure the file exists")
 10     return value
 11
 12
 13 def valid_http_url(value):
 14     if (value is None or re.compile(
 15             r'^https?://'  # http:// or https://
 16             r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'  # domain...
 17             r'localhost|'  # localhost...
 18             r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
 19             r'(?::\d+)?'  # optional port
 20             r'(?:/?|[/?]\S+)$', re.IGNORECASE).search(value) is None):
 21         raise ValueError("Please set an valid HTTP(S) URL")
 22     return value
 23
 24
 25 class DataSourceType(Enum):
 26     NOT_DEFINED = (0, None)
 27     HTTP = (100, valid_http_url)
 28     LOCAL_FILE = (200, valid_file_path)
 29
 30     def __init__(self, enum_id, enum_validator):
 31         self._id = enum_id
 32         self._validator = enum_validator
 33
 34     @property
 35     def validator(self):
 36         return self._validator
 37
 38
 39 class ValidDataSourceType(object):
 40     def __init__(self):
 41         self.default = DataSourceType.NOT_DEFINED
 42         self.values = WeakKeyDictionary()
 43
 44     def __get__(self, instance, owner):
 45         return self.values.get(instance, self.default)
 46
 47     def __set__(self, instance, value):
 48         if value is None or not DataSourceType.__contains__(DataSourceType[value]):
 49             raise ValueError("Please set a valid Data Source Type Enum, "
 50                              " possible values are -> ", [e.name for e in DataSourceType])
 51         self.values[instance] = DataSourceType[value]
 52
 53     def __delete__(self, instance):
 54         del self.values[instance]
 55
 56
 57 class ValidDataSourcePath(object):
 58     def __init__(self, default_data_source_type_field='data_source_type'):
 59         self._default = ''
 60         self._default_data_source_type_field = default_data_source_type_field
 61         self.values = WeakKeyDictionary()
 62
 63     def __get__(self, instance, owner):
 64         return self.values.get(instance, self._default)
 65
 66     def __set__(self, instance, *value):
 67         data_source_type_field = self._default_data_source_type_field
 68         value_to_set = None
 69
 70         if value and len(value) == 1 and isinstance(value[0], str):  # user sent only the value
 71             value_to_set = value[0]
 72         if value and len(value) == 1 and isinstance(value[0], tuple):  # user sent the value , and the validation field
 73             value_to_set = value[0][0]
 74             data_source_type_field = value[0][1]
 75
 76         _data_source_type = getattr(instance, data_source_type_field, None)
 77         if not _data_source_type:
 78             raise ValueError(" Valid source path depends on ValidDataSourceType , "
 79                              " please make sure you have an attribute named ValidDataSourceType")
 80         _data_source_type.validator(value_to_set)
 81         self.values[instance] = value_to_set
 82
 83
 84 class DataSource(object):
 85     data_source_type = ValidDataSourceType()
 86     data_source_path = ValidDataSourcePath()
 87
 88
 89 class SomeOtherDomainModel(object):
 90     data_source_type_ext = ValidDataSourceType()
 91     data_source_path = ValidDataSourcePath()
 92
 93
 94 print(" **************** Scenario 1 - Start **************** ")
 95 ds1 = DataSource()
 96 ds1.data_source_type = 'HTTP'
 97 ds1.data_source_path = "http://www.google.com"
 98 print(ds1.data_source_path)
 99 print(" **************** Scenario 1 - End **************** ")
100
101 print(" **************** Scenario 2 - Start **************** ")
102 ds2 = SomeOtherDomainModel()
103 ds2.data_source_type_ext = 'HTTP'
104 ds2.data_source_path = ("http://www.yahoo.com", 'data_source_type_ext')
105 print(ds2.data_source_path)
106 print(" **************** Scenario 2 - Start **************** ")

1 个答案:

答案 0 :(得分:1)

所以,建立我上面的评论,这里是DataSource的外观(使用request.Credentials = new NetworkCredential("Username", "Password", "Domain"); ),以及验证器类应该只是返回路径是否是布尔值的函数的想法有效(如果需要,会引发错误),而不是更多的描述符:

@property

描述符可能很难与周围一起工作(我应该知道;我确实wrote the book),并且当找到更简单的解决方案时应该避免这样做,这就是为什么我将验证器转换为谓词函数。使用class DataSource(object): data_source_type = ValidDataSourceType() @property def data_source_path(self): # can put in a check to make sure _data_source_path exists return _data_source_path @data_source_path.setter def data_source_path(self, path): if self.data_source_type.validator(path): self._data_source_path = path 而不是自定义描述符也没有羞耻感。