sweetrpg-db 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sweetrpg-db-0.0.1/LICENSE +21 -0
- sweetrpg-db-0.0.1/PKG-INFO +19 -0
- sweetrpg-db-0.0.1/README.md +1 -0
- sweetrpg-db-0.0.1/setup.cfg +60 -0
- sweetrpg-db-0.0.1/setup.py +15 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/__init__.py +15 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/exceptions.py +11 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/mongodb/__init__.py +5 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/mongodb/options.py +104 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/mongodb/repo.py +238 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/schema/__init__.py +5 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db/utils.py +5 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db.egg-info/PKG-INFO +19 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db.egg-info/SOURCES.txt +17 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db.egg-info/dependency_links.txt +1 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db.egg-info/entry_points.txt +2 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db.egg-info/requires.txt +6 -0
- sweetrpg-db-0.0.1/src/sweetrpg_db.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 SweetRPG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sweetrpg-db
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Home-page: https://sweetrpg.com
|
|
5
|
+
Author: Paul Schifferer
|
|
6
|
+
Author-email: dm@sweetrpg.com
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Documentation, https://sweetrpg.github.io/db
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Flask
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
|
|
19
|
+
# db.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# db.py
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[bdist_wheel]
|
|
2
|
+
universal = 1
|
|
3
|
+
|
|
4
|
+
[metadata]
|
|
5
|
+
name = sweetrpg-db
|
|
6
|
+
version = attr: sweetrpg_db.__version__
|
|
7
|
+
url = https://sweetrpg.com
|
|
8
|
+
project_urls =
|
|
9
|
+
Documentation = https://sweetrpg.github.io/db
|
|
10
|
+
license = MIT
|
|
11
|
+
author = Paul Schifferer
|
|
12
|
+
author_email = dm@sweetrpg.com
|
|
13
|
+
long_description = file: README.md
|
|
14
|
+
classifiers =
|
|
15
|
+
Development Status :: 4 - Beta
|
|
16
|
+
Environment :: Web Environment
|
|
17
|
+
Framework :: Flask
|
|
18
|
+
Intended Audience :: Developers
|
|
19
|
+
License :: OSI Approved :: MIT License
|
|
20
|
+
Operating System :: OS Independent
|
|
21
|
+
Programming Language :: Python
|
|
22
|
+
Topic :: Software Development :: Libraries :: Application Frameworks
|
|
23
|
+
|
|
24
|
+
[options]
|
|
25
|
+
packages = find:
|
|
26
|
+
package_dir = = src
|
|
27
|
+
include_package_data = true
|
|
28
|
+
python_required = >=3.12
|
|
29
|
+
|
|
30
|
+
[options.packages.find]
|
|
31
|
+
where = src
|
|
32
|
+
|
|
33
|
+
[options.entry_points]
|
|
34
|
+
console_scripts =
|
|
35
|
+
sweetrpg-db = sweetrpg_db.cli:main
|
|
36
|
+
|
|
37
|
+
[tool:pytest]
|
|
38
|
+
testpaths = tests
|
|
39
|
+
filterwarnings =
|
|
40
|
+
error
|
|
41
|
+
|
|
42
|
+
[coverage:run]
|
|
43
|
+
branch = True
|
|
44
|
+
source =
|
|
45
|
+
sweetrpg_db
|
|
46
|
+
tests
|
|
47
|
+
|
|
48
|
+
[coverage:paths]
|
|
49
|
+
source =
|
|
50
|
+
src
|
|
51
|
+
*/site-packages
|
|
52
|
+
|
|
53
|
+
[semantic_release]
|
|
54
|
+
version_variable = src/sweetrpg_db/__init__.py:__version__
|
|
55
|
+
branch = develop
|
|
56
|
+
|
|
57
|
+
[egg_info]
|
|
58
|
+
tag_build =
|
|
59
|
+
tag_date = 0
|
|
60
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.
|
|
4
|
+
setup(
|
|
5
|
+
name="sweetrpg-db",
|
|
6
|
+
install_requires=[
|
|
7
|
+
"dnspython~=2.0",
|
|
8
|
+
"marshmallow~=3.0",
|
|
9
|
+
"mongoengine~=0.27",
|
|
10
|
+
"PyMongo[srv]~=3.0",
|
|
11
|
+
"sweetrpg-model-core",
|
|
12
|
+
"sweetrpg-common",
|
|
13
|
+
],
|
|
14
|
+
extras_require={},
|
|
15
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
__author__ = "Paul Schifferer <dm@sweetrpg.com>"
|
|
3
|
+
"""
|
|
4
|
+
Metadata.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__title__ = "sweetrpg-db"
|
|
8
|
+
__description__ = "SweetRPG database modules"
|
|
9
|
+
__url__ = "https://github.com/sweetrpg/db"
|
|
10
|
+
__version__ = "0.0.1"
|
|
11
|
+
__build__ = 0x000007
|
|
12
|
+
__author_email__ = "dm@sweetrpg.com"
|
|
13
|
+
__license__ = "MIT"
|
|
14
|
+
__copyright__ = "Copyright 2024 SweetRPG"
|
|
15
|
+
__cake__ = "\u2728 \U0001f370 \u2728"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
__author__ = "Paul Schifferer <dm@sweetrpg.com>"
|
|
3
|
+
"""
|
|
4
|
+
Query options for accessing MongoDB.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class QueryOptions(object):
|
|
9
|
+
"""An object to store query options for a PyMongo find*() call."""
|
|
10
|
+
|
|
11
|
+
# https://flask-rest-jsonapi.readthedocs.io/en/latest/filtering.html
|
|
12
|
+
_filter_operators = {
|
|
13
|
+
"eq": "$eq",
|
|
14
|
+
"gt": "$gt",
|
|
15
|
+
"ge": "$gte",
|
|
16
|
+
"in_": "$in",
|
|
17
|
+
"lt": "$lt",
|
|
18
|
+
"le": "$lte",
|
|
19
|
+
"ne": "$ne",
|
|
20
|
+
"notin_": "$nin",
|
|
21
|
+
# '': '$and',
|
|
22
|
+
"isnot": "$not",
|
|
23
|
+
# '': '$nor',
|
|
24
|
+
# '': '$or',
|
|
25
|
+
"is_": "$exists",
|
|
26
|
+
# 'any': 'TODO',
|
|
27
|
+
# 'between': 'TODO',
|
|
28
|
+
# 'endswith': 'TODO',
|
|
29
|
+
# 'has': 'TODO',
|
|
30
|
+
# 'ilike': 'TODO',
|
|
31
|
+
# 'like': 'TODO',
|
|
32
|
+
# 'match': 'TODO',
|
|
33
|
+
# 'notilike': 'TODO',
|
|
34
|
+
# 'notlike': 'TODO',
|
|
35
|
+
# 'startswith': 'TODO',
|
|
36
|
+
}
|
|
37
|
+
_sort_values = {
|
|
38
|
+
"asc": 1,
|
|
39
|
+
"dsc": -1,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def __init__(self, filters: dict = {}, projection: list = [], skip: int = 0, limit: int = 0, sort: list = []):
|
|
43
|
+
"""Initialize the QueryOptions object.
|
|
44
|
+
:param dict filters: A dictionary of filters to apply to the query.
|
|
45
|
+
:param list projection: A list of attribute names to include in the returned result. If `None`, all attributes are returned.
|
|
46
|
+
:param int skip: An offset to use for pagination.
|
|
47
|
+
:param int limit: The maximum number of results to return.
|
|
48
|
+
:param list sort: A list of key-value pairs specifying the attributes to sort on.
|
|
49
|
+
"""
|
|
50
|
+
self.filters = filters
|
|
51
|
+
self.projection = projection
|
|
52
|
+
self.skip = skip
|
|
53
|
+
self.limit = limit
|
|
54
|
+
self.sort = sort
|
|
55
|
+
|
|
56
|
+
def __repr__(self):
|
|
57
|
+
return f"<QueryOptions(filters={self.filters}, projection={self.projection}, skip={self.skip}, limit={self.limit}, sort={self.sort})>"
|
|
58
|
+
|
|
59
|
+
def _process_filter(self, filter_info: dict):
|
|
60
|
+
name = filter_info["name"]
|
|
61
|
+
value = filter_info["val"]
|
|
62
|
+
op = self._filter_operators.get(filter_info["op"], "$eq")
|
|
63
|
+
return {name: {op: value}}
|
|
64
|
+
|
|
65
|
+
def set_filters(self, filters: dict = None, from_querystring: list = None):
|
|
66
|
+
"""Sets filters for the query.
|
|
67
|
+
|
|
68
|
+
:param dict filters: A dictionary of filters to set.
|
|
69
|
+
:param list from_querystring: Filters to set in querystring format.
|
|
70
|
+
"""
|
|
71
|
+
if filters is not None:
|
|
72
|
+
self.filters = filters
|
|
73
|
+
elif from_querystring is not None:
|
|
74
|
+
filters = {}
|
|
75
|
+
for f in from_querystring:
|
|
76
|
+
filters.update(self._process_filter(f))
|
|
77
|
+
self.filters = filters
|
|
78
|
+
|
|
79
|
+
def set_projection(self, projection: list = None, from_querystring: list = None):
|
|
80
|
+
"""Sets projections for the query.
|
|
81
|
+
|
|
82
|
+
:param list projection: A list of field names to include in the query results.
|
|
83
|
+
:param list from_querystring: Projections to set in querystring format.
|
|
84
|
+
"""
|
|
85
|
+
if projection is not None:
|
|
86
|
+
self.projection = projection
|
|
87
|
+
elif from_querystring is not None:
|
|
88
|
+
self.projection = from_querystring
|
|
89
|
+
|
|
90
|
+
def _process_sort(self, sort_item: dict):
|
|
91
|
+
name = sort_item["field"]
|
|
92
|
+
direction = self._sort_values.get(sort_item["order"], 1)
|
|
93
|
+
return name, direction
|
|
94
|
+
|
|
95
|
+
def set_sort(self, sort: list = None, from_querystring: list = None):
|
|
96
|
+
"""Sets sorting for the query results.
|
|
97
|
+
|
|
98
|
+
:param list sort: A list of dictionaries containing field name and ordering.
|
|
99
|
+
:param list from_querystring: Sorts to set in querystring format.
|
|
100
|
+
"""
|
|
101
|
+
if sort is not None:
|
|
102
|
+
self.sort = sort
|
|
103
|
+
elif from_querystring is not None:
|
|
104
|
+
self.sort = list(map(self._process_sort, from_querystring))
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
__author__ = "Paul Schifferer <dm@sweetrpg.com>"
|
|
3
|
+
"""
|
|
4
|
+
MongoDB repository module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ..exceptions import ObjectNotFound
|
|
8
|
+
from bson.objectid import ObjectId
|
|
9
|
+
from bson.timestamp import Timestamp
|
|
10
|
+
import datetime
|
|
11
|
+
from .options import QueryOptions
|
|
12
|
+
from pymongo.write_concern import WriteConcern
|
|
13
|
+
import logging
|
|
14
|
+
from mongoengine.queryset import QuerySet
|
|
15
|
+
from mongoengine import Document
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MongoDataRepository(object):
|
|
19
|
+
"""A repository class for interacting with a MongoDB database."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, **kwargs):
|
|
22
|
+
"""Create a MongoDB repository instance.
|
|
23
|
+
|
|
24
|
+
:param kwargs: Keyword arguments for setting up the repository connection.
|
|
25
|
+
:key model: The class of the model for this connection.
|
|
26
|
+
:key document: The class of the document for this connection.
|
|
27
|
+
:key db: A :class:`PyMongo` object used for connecting to the database.
|
|
28
|
+
"""
|
|
29
|
+
self.model_class = kwargs["model"]
|
|
30
|
+
self.document_class = kwargs["document"]
|
|
31
|
+
# self.db = kwargs.get("db")
|
|
32
|
+
# print(dir(self.document_class))
|
|
33
|
+
self.collection = kwargs["collection"] # self.document_class.meta["collection"]
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"<MongoDataRepository(model_class={self.model_class}, document_class={self.document_class}, collection={self.collection})>"
|
|
37
|
+
|
|
38
|
+
def _handle_value(self, value):
|
|
39
|
+
"""Convert a value to a string.
|
|
40
|
+
|
|
41
|
+
:param any value: The value to convert. Supports :class:`bson.objectid.ObjectId`,
|
|
42
|
+
:class:`datetime.datetime`, :class:`bson.timestamp.Timestamp`, and lists of
|
|
43
|
+
any of those types.
|
|
44
|
+
:return str: A string of the specified value.
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(value, ObjectId):
|
|
47
|
+
logging.debug("converting ObjectId('%s')...", value)
|
|
48
|
+
return str(value)
|
|
49
|
+
elif isinstance(value, datetime.datetime):
|
|
50
|
+
logging.debug("converting datetime value '%s'...", value)
|
|
51
|
+
d = value.replace(tzinfo=datetime.timezone.utc)
|
|
52
|
+
return d.isoformat(timespec="milliseconds")
|
|
53
|
+
elif isinstance(value, Timestamp):
|
|
54
|
+
logging.debug("converting Timestamp '%s'...", value)
|
|
55
|
+
return value.as_datetime()
|
|
56
|
+
elif isinstance(value, list):
|
|
57
|
+
logging.debug("converting list '%s'...", value)
|
|
58
|
+
return list(map(self._handle_value, value))
|
|
59
|
+
|
|
60
|
+
logging.debug("returning unprocessed value '%s'...", value)
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
def _modify_record(self, record: dict) -> dict:
|
|
64
|
+
"""Modify a record by converting any values to strings, and renaming the internal '_id'
|
|
65
|
+
field to 'id'.
|
|
66
|
+
|
|
67
|
+
:param dict record: The record to modify.
|
|
68
|
+
:return dict: The modified record.
|
|
69
|
+
"""
|
|
70
|
+
modified_record = {}
|
|
71
|
+
for k, v in record.items():
|
|
72
|
+
logging.debug("k: %s, v: %s", k, v)
|
|
73
|
+
if k == "_id":
|
|
74
|
+
k = "id"
|
|
75
|
+
modified_value = self._handle_value(v)
|
|
76
|
+
modified_record[k] = modified_value
|
|
77
|
+
logging.debug("k: %s, v (modified): %s", k, modified_value)
|
|
78
|
+
|
|
79
|
+
return modified_record
|
|
80
|
+
|
|
81
|
+
def _adjust_sort(self, sort: tuple) -> str:
|
|
82
|
+
if sort[1] < 0:
|
|
83
|
+
return f"-{sort[0]}"
|
|
84
|
+
return f"+{sort[0]}"
|
|
85
|
+
|
|
86
|
+
def create(self, data: dict) -> Document:
|
|
87
|
+
"""Inserts a new object in the database with the data provided.
|
|
88
|
+
|
|
89
|
+
:param dict data: The data for the object
|
|
90
|
+
:return Document: The inserted document.
|
|
91
|
+
"""
|
|
92
|
+
logging.debug("data: %s", data)
|
|
93
|
+
|
|
94
|
+
# collection = self.db[self.collection]
|
|
95
|
+
logging.info("Creating new %s record with data %s...", self.document_class.__name__, data)
|
|
96
|
+
doc = self.document_class(**data)
|
|
97
|
+
logging.debug("doc: %s", doc)
|
|
98
|
+
# result = collection.with_options(write_concern=WriteConcern(w=3, j=True)).insert_one(data)
|
|
99
|
+
doc.validate()
|
|
100
|
+
doc.save()
|
|
101
|
+
logging.debug("saved doc: %s", doc)
|
|
102
|
+
|
|
103
|
+
return doc
|
|
104
|
+
|
|
105
|
+
def get(self, record_id: str, deleted: bool = False) -> Document:
|
|
106
|
+
"""Fetch a single record from the database.
|
|
107
|
+
|
|
108
|
+
:param str record_id: The identifier for the record to fetch. This value is compared against the attribute specified in `id_attr`.
|
|
109
|
+
:param bool deleted: Include "deleted" objects in the query
|
|
110
|
+
:return Document: An instance of the object type from `model_class`.
|
|
111
|
+
"""
|
|
112
|
+
logging.debug("record_id: %s", record_id)
|
|
113
|
+
id_value = record_id
|
|
114
|
+
if isinstance(id_value, str):
|
|
115
|
+
id_value = ObjectId(record_id)
|
|
116
|
+
logging.debug("id_value: %s", id_value)
|
|
117
|
+
query_filter = {"_id": id_value}
|
|
118
|
+
if not deleted:
|
|
119
|
+
query_filter.update({"deleted_at": {"$not": {"$type": "date"}}})
|
|
120
|
+
logging.debug("query_filter: %s", query_filter)
|
|
121
|
+
|
|
122
|
+
logging.info("Fetching %s record for ID %s...", self.document_class.__name__, id_value)
|
|
123
|
+
record = self.document_class.objects(__raw__=query_filter).first() # QuerySet(self.document_class, self.collection)
|
|
124
|
+
# print(f"qs: {qs}")
|
|
125
|
+
# logging.debug("qs: %s", qs)
|
|
126
|
+
# record = None # qs.get(**query_filter)
|
|
127
|
+
logging.debug("record: %s", record)
|
|
128
|
+
# if not record:
|
|
129
|
+
# raise ObjectNotFound(f"Record not found where for '{record_id}'")
|
|
130
|
+
|
|
131
|
+
return record
|
|
132
|
+
|
|
133
|
+
def query(self, options: QueryOptions, deleted: bool = False) -> list:
|
|
134
|
+
"""Perform a query for objects in the database.
|
|
135
|
+
|
|
136
|
+
:param QueryOptions options: (Optional) Options specifying limits to the query's returned results
|
|
137
|
+
:return list: Returns a list of Document-subclass instances matching the query.
|
|
138
|
+
"""
|
|
139
|
+
logging.debug("options: %s", options)
|
|
140
|
+
query_filter = options.filters or {}
|
|
141
|
+
if not deleted:
|
|
142
|
+
query_filter.update({"deleted_at": {"$not": {"$type": "date"}}})
|
|
143
|
+
logging.debug("query_filter: %s", query_filter)
|
|
144
|
+
|
|
145
|
+
logging.info("Searching for %s records matching filter %s...", self.document_class, query_filter)
|
|
146
|
+
records = (
|
|
147
|
+
self.document_class.objects(__raw__=query_filter)
|
|
148
|
+
.order_by(*list(map(self._adjust_sort, options.sort)))
|
|
149
|
+
.skip(options.skip)
|
|
150
|
+
.limit(options.limit)
|
|
151
|
+
.only(*options.projection)
|
|
152
|
+
)
|
|
153
|
+
# qs = QuerySet(self.document_class, self.collection)
|
|
154
|
+
# if options.skip > 0:
|
|
155
|
+
# qs.skip(options.skip)
|
|
156
|
+
# if options.limit > 0:
|
|
157
|
+
# qs.limit(options.limit)
|
|
158
|
+
# if len(options.projection) > 0:
|
|
159
|
+
# qs.only(*options.projection)
|
|
160
|
+
# if len(options.sort) > 0:
|
|
161
|
+
# qs.order_by(*options.sort)
|
|
162
|
+
# print(f"qs: {qs}")
|
|
163
|
+
# logging.debug("qs: %s", qs)
|
|
164
|
+
# query_set = self.document_class.objects.skip(options.skip).limit(options.limit).order_by(options.sort).only(*options.projection)
|
|
165
|
+
# records = list(qs.all()) # .all()
|
|
166
|
+
logging.debug("records: %s", records)
|
|
167
|
+
|
|
168
|
+
# modified_records = map(self._modify_record, records)
|
|
169
|
+
# logging.debug("modified_records: %s", modified_records)
|
|
170
|
+
|
|
171
|
+
return list(records)
|
|
172
|
+
|
|
173
|
+
def update(self, record_id: str, update: dict, deleted: bool = False) -> Document:
|
|
174
|
+
"""Update the specified record.
|
|
175
|
+
|
|
176
|
+
:param str record_id: The ID of the record to update.
|
|
177
|
+
:param dict update: The data to update for the record.
|
|
178
|
+
:param bool deleted: Indicates whether the update operation should look for deleted records.
|
|
179
|
+
:return Document: The update version of the object.
|
|
180
|
+
"""
|
|
181
|
+
id_value = record_id
|
|
182
|
+
if isinstance(id_value, str):
|
|
183
|
+
id_value = ObjectId(record_id)
|
|
184
|
+
# if self.id_attr == "_id":
|
|
185
|
+
# logging.debug("ID attribute is '_id', converting to ObjectId")
|
|
186
|
+
# id_value = ObjectId(record_id)
|
|
187
|
+
obj_filter = {"_id": id_value}
|
|
188
|
+
logging.debug("obj_filter: %s", obj_filter)
|
|
189
|
+
|
|
190
|
+
update_oper = {"$set": update}
|
|
191
|
+
logging.debug("update_oper: %s", update_oper)
|
|
192
|
+
|
|
193
|
+
logging.info("Marking %s record %s deleted...", self.model_class, id_value)
|
|
194
|
+
query_filter = {"_id": id_value}
|
|
195
|
+
if not deleted:
|
|
196
|
+
query_filter.update({"deleted_at": {"$not": {"$type": "date"}}})
|
|
197
|
+
logging.debug("query_filter: %s", query_filter)
|
|
198
|
+
# doc = self.document_class.objects.raw(query_filter).update(update_oper)
|
|
199
|
+
doc = self.get(record_id, deleted=deleted)
|
|
200
|
+
# if doc is None:
|
|
201
|
+
# logging.info("No document found to update for record ID %s.", record_id)
|
|
202
|
+
# return False
|
|
203
|
+
logging.debug("doc: %s", doc)
|
|
204
|
+
doc.update(**update)
|
|
205
|
+
|
|
206
|
+
return doc
|
|
207
|
+
|
|
208
|
+
def delete(self, record_id: str, actually: bool = False) -> bool:
|
|
209
|
+
"""'Delete' the specified record. Deletion is accomplished by setting the `deleted_at` field to the current
|
|
210
|
+
timestamp, so that queries for the object will ignore it.
|
|
211
|
+
|
|
212
|
+
:param str record_id: The record ID of the object to delete. This can be a string or :class:`bson.objectid.ObjectId`.
|
|
213
|
+
:param bool actually: Actually delete the record instead of just marking it "deleted".
|
|
214
|
+
:return bool: A boolean indicating whether the record was able to be marked deleted.
|
|
215
|
+
:raises DoesNotExist:
|
|
216
|
+
"""
|
|
217
|
+
id_value = record_id
|
|
218
|
+
if isinstance(id_value, str):
|
|
219
|
+
id_value = ObjectId(record_id)
|
|
220
|
+
doc = self.get(record_id)
|
|
221
|
+
# if doc is None:
|
|
222
|
+
# logging.info("No document found to delete for record ID %s.", record_id)
|
|
223
|
+
# return False
|
|
224
|
+
|
|
225
|
+
if actually:
|
|
226
|
+
logging.info("Deleting %s record %s...", self.model_class.__name__, id_value)
|
|
227
|
+
doc.delete()
|
|
228
|
+
return True
|
|
229
|
+
else:
|
|
230
|
+
logging.info("Marking %s record %s deleted...", self.model_class.__name__, id_value)
|
|
231
|
+
now = datetime.datetime.utcnow()
|
|
232
|
+
doc.deleted_at = now
|
|
233
|
+
updated_doc = doc.save()
|
|
234
|
+
logging.debug("updated_doc: %s", updated_doc)
|
|
235
|
+
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
return False
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sweetrpg-db
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Home-page: https://sweetrpg.com
|
|
5
|
+
Author: Paul Schifferer
|
|
6
|
+
Author-email: dm@sweetrpg.com
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Documentation, https://sweetrpg.github.io/db
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Flask
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
|
|
19
|
+
# db.py
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.cfg
|
|
4
|
+
setup.py
|
|
5
|
+
src/sweetrpg_db/__init__.py
|
|
6
|
+
src/sweetrpg_db/exceptions.py
|
|
7
|
+
src/sweetrpg_db/utils.py
|
|
8
|
+
src/sweetrpg_db.egg-info/PKG-INFO
|
|
9
|
+
src/sweetrpg_db.egg-info/SOURCES.txt
|
|
10
|
+
src/sweetrpg_db.egg-info/dependency_links.txt
|
|
11
|
+
src/sweetrpg_db.egg-info/entry_points.txt
|
|
12
|
+
src/sweetrpg_db.egg-info/requires.txt
|
|
13
|
+
src/sweetrpg_db.egg-info/top_level.txt
|
|
14
|
+
src/sweetrpg_db/mongodb/__init__.py
|
|
15
|
+
src/sweetrpg_db/mongodb/options.py
|
|
16
|
+
src/sweetrpg_db/mongodb/repo.py
|
|
17
|
+
src/sweetrpg_db/schema/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sweetrpg_db
|