This repository has been archived by the owner on Nov 16, 2020. It is now read-only.
/
domain.py
166 lines (142 loc) · 6.8 KB
/
domain.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
from bson.objectid import ObjectId
from flask import current_app
from passlib.utils import classproperty
from pydash import merge
from pymongo import ReturnDocument
from pymongo.collection import Collection
from pymongo.cursor import Cursor
from ereuse_devicehub.data_layer import mongo_encode
from ereuse_devicehub.exceptions import StandardError
from ereuse_devicehub.resources.resource import ResourceSettings
class Domain:
"""Provides utility methods to work with resources, like easier access to data-layers. Extend it happily."""
resource_settings = ResourceSettings
"""Link to the resource settings. Override it linking it with the appropriate subclass"""
@classmethod
@mongo_encode('id_or_filter')
def get_one(cls, id_or_filter: dict or ObjectId or str) -> dict:
"""
Obtains a resource.
:param id_or_filter: An identifier or the filter in Mongo to obtain the resource.
:raise ResourceNotFound:
:return:
"""
resource = current_app.data.find_one_raw(cls.resource_name, id_or_filter)
if resource is None:
if type(id_or_filter) is dict:
text = 'There is no resource matching the query {}'.format(id_or_filter)
else:
text = 'The resource with id {} does not exist.'.format(id_or_filter)
raise ResourceNotFound(text)
else:
return resource
@classmethod
@mongo_encode('query_filter')
def get(cls, query_filter: dict, as_list: bool = True) -> list or Cursor:
"""
Obtains several resources.
:param query_filter: A Mongo filter to obtain the resources.
:param as_list: Should be returned as a list or as a generator?
"""
result = current_app.data.find_raw(cls.resource_name, query_filter)
return list(result) if as_list else result
@classmethod
@mongo_encode('values')
def get_in(cls, field: str, values: list, as_list: bool = True):
"""The same as get({field: {$in: values}})."""
return cls.get({field: {'$in': values}})
@classmethod
@mongo_encode('operation')
def update_raw(cls, ids: str or ObjectId or list, operation: dict):
"""
Sets the properties of a resource using directly the database layer.
:param ids:
:param operation: MongoDB update query.
:return The number of files edited in total
"""
resources_id = [ids] if type(ids) is str or type(ids) is ObjectId else ids
count = 0
for resource_id in resources_id:
count += cls.collection.update_one({'_id': resource_id}, operation).modified_count
return count
@classmethod
@mongo_encode('filter', 'operation')
def update_many_raw(cls, filter, operation):
return cls.collection.update_many(filter, operation)
@classmethod
@mongo_encode('id_or_filter', 'operation')
def update_one_raw(cls, resource_id: str or ObjectId, operation, key='_id'):
count = cls.collection.update_one({key: resource_id}, operation).matched_count
if count == 0:
name = cls.resource_settings._schema.type_name
raise ResourceNotFound('{} {} cannot be updated as it is not found.'.format(name, resource_id))
@classmethod
@mongo_encode('id_or_filter', 'operation')
def update_one_raw_get(cls, resource_id: str or ObjectId, operation, return_document=ReturnDocument.AFTER,
key='_id', **kwargs):
document = cls.collection.find_one_and_update({key: resource_id}, operation, return_document=return_document,
**kwargs)
if document is None:
name = cls.resource_settings._schema.type_name
raise ResourceNotFound('{} {} cannot be updated as it is not found.'.format(name, resource_id))
return document
@classmethod
@mongo_encode('operation', 'extra_query')
def update_raw_get(cls, ids: str or ObjectId or list, operation: dict, key='_id',
return_document=ReturnDocument.AFTER, extra_query={}, **kwargs):
"""Updates the resources and returns them. Set return_document to get the documents before/after the update."""
resources_id = [ids] if type(ids) is str or type(ids) is ObjectId else ids
results = []
for identifier in resources_id:
q = merge({key: identifier}, extra_query)
results.append(cls.collection.find_one_and_update(q, operation, return_document=return_document, **kwargs))
return results
@classmethod
@mongo_encode('query')
def delete_one(cls, query):
return cls.collection.delete_one(query)
@classmethod
def delete_all(cls):
return cls.collection.drop()
@classmethod
def path_for(cls, database: str, identifier: str or ObjectId or int) -> str:
"""Returns the resource path for a given identifier."""
path = current_app.config['DOMAIN'][cls.resource_settings.resource_name()]['url']
return '{}/{}/{}'.format(database, path, str(identifier))
@classmethod
def url_agent_for(cls, database: str, identifier: str or ObjectId or int):
"""Returns the resource full url as seen for other agents."""
return '{}/{}'.format(current_app.config['BASE_URL_FOR_AGENTS'], cls.path_for(database, identifier))
@classmethod
def insert(cls, document: dict):
"""Inserts a document."""
current_app.data.insert(cls.resource_name, document)
@classmethod
@mongo_encode('filter')
def count(cls, filter: dict = None, **kwargs):
"""The same as in pymongo.collection.Collection.count"""
return cls.collection.count(filter, **kwargs)
@classproperty
def resource_name(cls) -> str:
return cls.resource_settings.resource_name()
@classproperty
def collection(cls) -> Collection:
"""Gets the collection in the correct database for the current resource and logged-in account."""
# (which is the same as the resource)
try:
# Collection name is usually the name of the resource, but not always (ex. for components is devices)
collection_name = cls.resource_settings.datasource['source']
except AttributeError:
raise AttributeError('Make sure resource_settings points to the correct subclass of ResourceSettings.')
else:
# pymongo(resource) gets the db of our resource and db[resource] the collection
# This invokes data_layer.current_mongo_prefix, which selects the db
return current_app.data.pymongo(cls.resource_name).db[collection_name]
@classmethod
def create_indexes(cls, indexes):
cls.collection.create_indexes(indexes)
@classmethod
def drop_indexes(cls):
cls.collection.drop_indexes()
class ResourceNotFound(StandardError):
status_code = 422