No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

datasource.py 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. # -*- coding: utf-8 -*-
  2. import bson
  3. from bson.son import SON
  4. from collections import OrderedDict
  5. import pymongo
  6. from pymongo import MongoClient
  7. from pymongo.errors import BulkWriteError
  8. import urllib
  9. import lodel.datasource.mongodb.utils as utils
  10. from lodel.datasource.generic.datasource import GenericDataSource
  11. class MongoDbDataSourceError(Exception):
  12. pass
  13. class MongoDbDataSource(GenericDataSource):
  14. MANDATORY_CONNECTION_ARGS = ('host', 'port', 'login', 'password', 'dbname')
  15. ## @brief Instanciates a Database object given a connection name
  16. # @param connection_name str
  17. def __init__(self, connection_name='default'):
  18. connection_args = self._get_connection_args(connection_name)
  19. login, password, host, port, dbname = MongoDbDataSource._check_connection_args(connection_args)
  20. # Creating of the connection
  21. connection_string = 'mongodb://%s:%s@%s:%s' % (login, password, host, port)
  22. self.connection = MongoClient(connection_string)
  23. # Getting the database
  24. self.database = self.connection[dbname]
  25. ## @brief Gets the settings given a connection name
  26. # @param connection_name str
  27. # @return dict
  28. # @TODO Change the return value using the Lodel 2 settings module
  29. def _get_connection_args(self, connection_name):
  30. return {
  31. 'host': 'localhost',
  32. 'port': 27017,
  33. 'login': 'login', # TODO modifier la valeur
  34. 'password': 'password', # TODO modifier la valeur
  35. 'dbname': 'lodel'
  36. }
  37. ## @brief checks if the connection args are valid and complete
  38. # @param connection_args dict
  39. # @return bool
  40. # @todo checks on the argument types can be added here
  41. @classmethod
  42. def _check_connection_args(cls, connection_args):
  43. errors = []
  44. for connection_arg in cls.MANDATORY_CONNECTION_ARGS:
  45. if connection_arg not in connection_args:
  46. errors.append("Datasource connection error : %s parameter is missing." % connection_arg)
  47. if len(errors) > 0 :
  48. raise MongoDbDataSourceError("\r\n-".join(errors))
  49. return (connection_args['login'], urllib.quote_plus(connection_args['password']), connection_args['host'],
  50. connection_args['port'], connection_args['dbname'])
  51. ## @brief returns a selection of documents from the datasource
  52. # @param target_cls Emclass
  53. # @param field_list list
  54. # @param filters list : List of filters
  55. # @param rel_filters list : List of relational filters
  56. # @param order list : List of column to order. ex: order = [('title', 'ASC'),]
  57. # @param group list : List of tupple representing the column used as "group by" fields. ex: group = [('title', 'ASC'),]
  58. # @param limit int : Number of records to be returned
  59. # @param offset int: used with limit to choose the start record
  60. # @param instanciate bool : If true, the records are returned as instances, else they are returned as dict
  61. # @return list
  62. # @todo Implement the relations
  63. def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0,
  64. instanciate=True):
  65. collection_name = utils.object_collection_name(target_cls.__class__)
  66. collection = self.database[collection_name]
  67. query_filters = utils.parse_query_filters(filters)
  68. query_result_ordering = utils.parse_query_order(order) if order is not None else None
  69. results_field_list = None if len(field_list) == 0 else field_list
  70. limit = limit if limit is not None else 0
  71. if group is None:
  72. cursor = collection.find(
  73. filter=query_filters,
  74. projection=results_field_list,
  75. skip=offset,
  76. limit=limit,
  77. sort=query_result_ordering
  78. )
  79. else:
  80. pipeline = list()
  81. unwinding_list = list()
  82. grouping_dict = OrderedDict()
  83. sorting_list = list()
  84. for group_param in group:
  85. field_name = group_param[0]
  86. field_sort_option = group_param[1]
  87. sort_option = utils.MONGODB_SORT_OPERATORS_MAP[field_sort_option]
  88. unwinding_list.append({'$unwind': '$%s' % field_name})
  89. grouping_dict[field_name] = '$%s' % field_name
  90. sorting_list.append((field_name, sort_option))
  91. sorting_list.extends(query_result_ordering)
  92. pipeline.append({'$match': query_filters})
  93. if results_field_list is not None:
  94. pipeline.append({'$project': SON([{field_name: 1} for field_name in field_list])})
  95. pipeline.extend(unwinding_list)
  96. pipeline.append({'$group': grouping_dict})
  97. pipeline.extend({'$sort': SON(sorting_list)})
  98. if offset > 0:
  99. pipeline.append({'$skip': offset})
  100. if limit is not None:
  101. pipeline.append({'$limit': limit})
  102. results = list()
  103. for document in cursor:
  104. results.append(document)
  105. return results
  106. ## @brief Deletes one record defined by its uid
  107. # @param target_cls Emclass : class of the record to delete
  108. # @param uid dict|list : a dictionary of fields and values composing the unique identifier of the record or a list of several dictionaries
  109. # @return int : number of deleted records
  110. # @TODO check the content of the result.raw_result property depending on the informations to return
  111. # @TODO Implement the error management
  112. def delete(self, target_cls, uid):
  113. if isinstance(uid, dict):
  114. uid = [uid]
  115. collection_name = utils.object_collection_name(target_cls.__class__)
  116. collection = self.database[collection_name]
  117. result = collection.delete_many(uid)
  118. return result.deleted_count
  119. ## @brief updates one or a list of records
  120. # @param target_cls Emclass : class of the object to insert
  121. # @param uids list : list of uids to update
  122. # @param datas dict : datas to update (new values)
  123. # @return int : Number of updated records
  124. # @todo check if the values need to be parsed
  125. def update(self, target_cls, uids, **datas):
  126. if not isinstance(uids, list):
  127. uids = [uids]
  128. collection_name = utils.object_collection_name(target_cls.__class__)
  129. collection = self.database[collection_name]
  130. results = collection.update_many({'uid': {'$in': uids}}, datas)
  131. return results.modified_count()
  132. ## @brief Inserts a record in a given collection
  133. # @param target_cls Emclass : class of the object to insert
  134. # @param datas dict : datas to insert
  135. # @return bool
  136. # @TODO Implement the error management
  137. def insert(self, target_cls, **datas):
  138. collection_name = utils.object_collection_name(target_cls.__class__)
  139. collection = self.database[collection_name]
  140. result = collection.insert_one(datas)
  141. return len(result.inserted_id)
  142. ## @brief Inserts a list of records in a given collection
  143. # @param target_cls Emclass : class of the objects inserted
  144. # @param datas_list
  145. # @return list : list of the inserted records' ids
  146. # @TODO Implement the error management
  147. def insert_multi(self, target_cls, datas_list):
  148. collection_name = utils.object_collection_name(target_cls.__class__)
  149. collection = self.database[collection_name]
  150. result = collection.insert_many(datas_list)
  151. return len(result.inserted_ids)