|
11 | 11 | from six import add_metaclass
|
12 | 12 | from pynamodb.exceptions import DoesNotExist, TableDoesNotExist, TableError
|
13 | 13 | from pynamodb.throttle import NoThrottle
|
14 |
| -from pynamodb.attributes import Attribute, AttributeContainer, MapAttribute, ListAttribute |
| 14 | +from pynamodb.attributes import Attribute, AttributeContainer, MapAttribute, ListAttribute, UnicodeSetAttribute |
15 | 15 | from pynamodb.connection.base import MetaTable
|
16 | 16 | from pynamodb.connection.table import TableConnection
|
17 | 17 | from pynamodb.connection.util import pythonic
|
@@ -233,6 +233,95 @@ def __init__(self, hash_key=None, range_key=None, **attrs):
|
233 | 233 | attrs[self._dynamo_to_python_attr(range_keyname)] = range_key
|
234 | 234 | self._set_attributes(**attrs)
|
235 | 235 |
|
| 236 | + @classmethod |
| 237 | + def fix_unicode_set_attributes(cls, |
| 238 | + get_save_kwargs, |
| 239 | + read_capacity_to_consume_per_second=10, |
| 240 | + max_sleep_between_retry=10, |
| 241 | + max_consecutive_exceptions=30): |
| 242 | + """ |
| 243 | + This function performs a rate limited scan of the table and re-serializes any UnicodeSetAttributes. |
| 244 | +
|
| 245 | + See https://github.com/pynamodb/PynamoDB/issues/377 for why this is necessary. |
| 246 | +
|
| 247 | + :param get_save_kwargs: A callback function that is passed a model and should return the kwargs |
| 248 | + used when conditionally saving the item |
| 249 | + :param read_capacity_to_consume_per_second: Amount of read capacity to consume |
| 250 | + every second |
| 251 | + :param max_sleep_between_retry: Max value for sleep in seconds in between scans during |
| 252 | + throttling/rate limit scenarios |
| 253 | + :param max_consecutive_exceptions: Max number of consecutive provision throughput exceeded |
| 254 | + exceptions for scan to exit |
| 255 | + """ |
| 256 | + |
| 257 | + if not cls._has_unicode_set_attribute(): |
| 258 | + return |
| 259 | + |
| 260 | + items = cls.rate_limited_scan( |
| 261 | + read_capacity_to_consume_per_second=read_capacity_to_consume_per_second, |
| 262 | + max_sleep_between_retry=max_sleep_between_retry, |
| 263 | + max_consecutive_exceptions=max_consecutive_exceptions |
| 264 | + ) |
| 265 | + for item in items: |
| 266 | + save_kwargs = get_save_kwargs(item) |
| 267 | + item.save(**save_kwargs) |
| 268 | + |
| 269 | + @classmethod |
| 270 | + def _has_unicode_set_attribute(cls): |
| 271 | + for attr_value in cls._get_attributes().values(): |
| 272 | + if isinstance(attr_value, UnicodeSetAttribute): |
| 273 | + return True |
| 274 | + if isinstance(attr_value, MapAttribute): |
| 275 | + return attr_value._has_unicode_set_attribute() |
| 276 | + return False |
| 277 | + |
| 278 | + @classmethod |
| 279 | + def needs_unicode_set_fix(cls, |
| 280 | + read_capacity_to_consume_per_second=10, |
| 281 | + max_sleep_between_retry=10, |
| 282 | + max_consecutive_exceptions=30): |
| 283 | + |
| 284 | + if not cls._has_unicode_set_attribute(): |
| 285 | + return False |
| 286 | + |
| 287 | + scan_result = cls._get_connection().rate_limited_scan( |
| 288 | + read_capacity_to_consume_per_second=read_capacity_to_consume_per_second, |
| 289 | + max_sleep_between_retry=max_sleep_between_retry, |
| 290 | + max_consecutive_exceptions=max_consecutive_exceptions, |
| 291 | + ) |
| 292 | + |
| 293 | + ret_val = False |
| 294 | + for item in scan_result: |
| 295 | + ret_val |= cls._has_json_unicode_set_value(item) |
| 296 | + return ret_val |
| 297 | + |
| 298 | + @classmethod |
| 299 | + def _has_json_unicode_set_value(cls, data): |
| 300 | + ret_val = False |
| 301 | + for name, attr in data.items(): |
| 302 | + # attr should be a map of attribute type to value |
| 303 | + (attr_type, attr_value), = attr.items() |
| 304 | + if attr_type == 'M': |
| 305 | + ret_val |= cls._has_json_unicode_set_value(attr_value) |
| 306 | + elif attr_type == 'L': |
| 307 | + for value in attr_value: |
| 308 | + (at, av), = value.items() |
| 309 | + ret_val |= cls._attr_has_json_unicode_set_value(at, av) |
| 310 | + else: |
| 311 | + ret_val |= cls._attr_has_json_unicode_set_value(attr_type, attr_value) |
| 312 | + return ret_val |
| 313 | + |
| 314 | + @classmethod |
| 315 | + def _attr_has_json_unicode_set_value(cls, attr_type, value): |
| 316 | + if attr_type != 'SS': |
| 317 | + return False |
| 318 | + for val in value: |
| 319 | + try: |
| 320 | + result = json.loads(val) |
| 321 | + except ValueError: |
| 322 | + return False |
| 323 | + return True |
| 324 | + |
236 | 325 | @classmethod
|
237 | 326 | def has_map_or_list_attributes(cls):
|
238 | 327 | for attr_value in cls._get_attributes().values():
|
|
0 commit comments