1#!/usr/bin/env python
2"""An abstract for a collection of key_range.KeyRange objects."""
3
4
5
6from google.appengine.ext import key_range
7from mapreduce import namespace_range
8
9
10__all__ = [
11    "KeyRangesFactory",
12    "KeyRanges"]
13
14# pylint: disable=g-bad-name
15
16
17class KeyRangesFactory(object):
18  """Factory for KeyRanges."""
19
20  @classmethod
21  def create_from_list(cls, list_of_key_ranges):
22    """Create a KeyRanges object.
23
24    Args:
25      list_of_key_ranges: a list of key_range.KeyRange object.
26
27    Returns:
28      A _KeyRanges object.
29    """
30    return _KeyRangesFromList(list_of_key_ranges)
31
32  @classmethod
33  def create_from_ns_range(cls, ns_range):
34    """Create a KeyRanges object.
35
36    Args:
37      ns_range: a namespace_range.NameSpace Range object.
38
39    Returns:
40      A _KeyRanges object.
41    """
42    return _KeyRangesFromNSRange(ns_range)
43
44  @classmethod
45  def from_json(cls, json):
46    """Deserialize from json.
47
48    Args:
49      json: a dict of json compatible fields.
50
51    Returns:
52      a KeyRanges object.
53
54    Raises:
55      ValueError: if the json is invalid.
56    """
57    if json["name"] in _KEYRANGES_CLASSES:
58      return _KEYRANGES_CLASSES[json["name"]].from_json(json)
59    raise ValueError("Invalid json %s", json)
60
61
62class KeyRanges(object):
63  """An abstraction for a collection of key_range.KeyRange objects."""
64
65  def __iter__(self):
66    return self
67
68  def next(self):
69    """Iterator iteraface."""
70    raise NotImplementedError()
71
72  def to_json(self):
73    return {"name": self.__class__.__name__}
74
75  @classmethod
76  def from_json(cls):
77    raise NotImplementedError()
78
79  def __eq__(self):
80    raise NotImplementedError()
81
82  def __str__(self):
83    raise NotImplementedError()
84
85
86class _KeyRangesFromList(KeyRanges):
87  """Create KeyRanges from a list."""
88
89  def __init__(self, list_of_key_ranges):
90    self._key_ranges = list_of_key_ranges
91
92  def __eq__(self, other):
93    if not isinstance(other, self.__class__):
94      return False
95    return self._key_ranges == other._key_ranges
96
97  def next(self):
98    if self._key_ranges:
99      return self._key_ranges.pop()
100    raise StopIteration()
101
102  def __str__(self):
103    if len(self._key_ranges) == 1:
104      return "Single KeyRange %s" % (self._key_ranges[0])
105    if self._key_ranges:
106      return "From %s to %s" % (self._key_ranges[0], self._key_ranges[-1])
107    return "Empty KeyRange."
108
109  def to_json(self):
110    json = super(_KeyRangesFromList, self).to_json()
111    json.update(
112        {"list_of_key_ranges": [kr.to_json() for kr in self._key_ranges]})
113    return json
114
115  @classmethod
116  def from_json(cls, json):
117    return cls(
118        [key_range.KeyRange.from_json(kr) for kr in json["list_of_key_ranges"]])
119
120
121class _KeyRangesFromNSRange(KeyRanges):
122  """Create KeyRanges from a namespace range."""
123
124  def __init__(self, ns_range):
125    """Init."""
126    self._ns_range = ns_range
127    if self._ns_range is not None:
128      self._iter = iter(self._ns_range)
129      self._last_ns = None
130
131  def __eq__(self, other):
132    if not isinstance(other, self.__class__):
133      return False
134    return self._ns_range == other._ns_range
135
136  def __str__(self):
137    return str(self._ns_range)
138
139  def next(self):
140    if self._ns_range is None:
141      raise StopIteration()
142
143    self._last_ns = self._iter.next()
144    current_ns_range = self._ns_range
145    if self._last_ns == self._ns_range.namespace_end:
146      self._ns_range = None
147    return key_range.KeyRange(namespace=self._last_ns,
148                              _app=current_ns_range.app)
149
150  def to_json(self):
151    json = super(_KeyRangesFromNSRange, self).to_json()
152    ns_range = self._ns_range
153    if self._ns_range is not None and self._last_ns is not None:
154      ns_range = ns_range.with_start_after(self._last_ns)
155    if ns_range is not None:
156      json.update({"ns_range": ns_range.to_json_object()})
157    return json
158
159  @classmethod
160  def from_json(cls, json):
161    if "ns_range" in json:
162      return cls(
163          namespace_range.NamespaceRange.from_json_object(json["ns_range"]))
164    else:
165      return cls(None)
166
167
168_KEYRANGES_CLASSES = {
169    _KeyRangesFromList.__name__: _KeyRangesFromList,
170    _KeyRangesFromNSRange.__name__: _KeyRangesFromNSRange
171}
172