1"""SimpleDBBackend class with methods for supported APIs."""
2import re
3from boto3 import Session
4from collections import defaultdict
5from moto.core import BaseBackend, BaseModel
6from threading import Lock
7
8from .exceptions import InvalidDomainName, UnknownDomainName
9
10
11class FakeItem(BaseModel):
12    def __init__(self):
13        self.attributes = []
14        self.lock = Lock()
15
16    def get_attributes(self, names):
17        if not names:
18            return self.attributes
19        return [attr for attr in self.attributes if attr["name"] in names]
20
21    def put_attributes(self, attributes):
22        # Replacing attributes involves quite a few loops
23        # Lock this, so we know noone else touches this list while we're operating on it
24        with self.lock:
25            for attr in attributes:
26                if attr.get("replace", "false").lower() == "true":
27                    self._remove_attributes(attr["name"])
28                self.attributes.append(attr)
29
30    def _remove_attributes(self, name):
31        self.attributes = [attr for attr in self.attributes if attr["name"] != name]
32
33
34class FakeDomain(BaseModel):
35    def __init__(self, name):
36        self.name = name
37        self.items = defaultdict(FakeItem)
38
39    def get(self, item_name, attribute_names):
40        item = self.items[item_name]
41        return item.get_attributes(attribute_names)
42
43    def put(self, item_name, attributes):
44        item = self.items[item_name]
45        item.put_attributes(attributes)
46
47
48class SimpleDBBackend(BaseBackend):
49    def __init__(self, region_name=None):
50        self.region_name = region_name
51        self.domains = dict()
52
53    def reset(self):
54        region_name = self.region_name
55        self.__dict__ = {}
56        self.__init__(region_name)
57
58    def create_domain(self, domain_name):
59        self._validate_domain_name(domain_name)
60        self.domains[domain_name] = FakeDomain(name=domain_name)
61
62    def list_domains(self, max_number_of_domains, next_token):
63        """
64        The `max_number_of_domains` and `next_token` parameter have not been implemented yet - we simply return all domains.
65        """
66        return self.domains.keys(), None
67
68    def delete_domain(self, domain_name):
69        self._validate_domain_name(domain_name)
70        # Ignore unknown domains - AWS does the same
71        self.domains.pop(domain_name, None)
72
73    def _validate_domain_name(self, domain_name):
74        # Domain Name needs to have at least 3 chars
75        # Can only contain characters: a-z, A-Z, 0-9, '_', '-', and '.'
76        if not re.match("^[a-zA-Z0-9-_.]{3,}$", domain_name):
77            raise InvalidDomainName(domain_name)
78
79    def _get_domain(self, domain_name):
80        if domain_name not in self.domains:
81            raise UnknownDomainName()
82        return self.domains[domain_name]
83
84    def get_attributes(self, domain_name, item_name, attribute_names, consistent_read):
85        """
86        Behaviour for the consistent_read-attribute is not yet implemented
87        """
88        self._validate_domain_name(domain_name)
89        domain = self._get_domain(domain_name)
90        return domain.get(item_name, attribute_names)
91
92    def put_attributes(self, domain_name, item_name, attributes, expected):
93        """
94        Behaviour for the expected-attribute is not yet implemented.
95        """
96        self._validate_domain_name(domain_name)
97        domain = self._get_domain(domain_name)
98        domain.put(item_name, attributes)
99
100
101sdb_backends = {}
102for available_region in Session().get_available_regions("sdb"):
103    sdb_backends[available_region] = SimpleDBBackend(available_region)
104for available_region in Session().get_available_regions(
105    "sdb", partition_name="aws-us-gov"
106):
107    sdb_backends[available_region] = SimpleDBBackend(available_region)
108for available_region in Session().get_available_regions("sdb", partition_name="aws-cn"):
109    sdb_backends[available_region] = SimpleDBBackend(available_region)
110