refscan 0.2.3__tar.gz → 0.2.5__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.
- {refscan-0.2.3 → refscan-0.2.5}/PKG-INFO +1 -1
- {refscan-0.2.3 → refscan-0.2.5}/pyproject.toml +1 -1
- {refscan-0.2.3 → refscan-0.2.5}/refscan/cli/scan.py +2 -7
- {refscan-0.2.3 → refscan-0.2.5}/refscan/grapher.py +2 -8
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/Finder.py +58 -3
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/ReferenceList.py +29 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/helpers.py +43 -1
- {refscan-0.2.3 → refscan-0.2.5}/refscan/scanner.py +105 -6
- {refscan-0.2.3 → refscan-0.2.5}/LICENSE +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/README.md +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/__init__.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/cli/cli.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/cli/graph.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/cli/version.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/Reference.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/Violation.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/ViolationList.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/lib/constants.py +0 -0
- {refscan-0.2.3 → refscan-0.2.5}/refscan/templates/graph.template.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: refscan
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Command-line program that scans the NMDC MongoDB database for referential integrity violations
|
|
5
5
|
Keywords: mongodb,mongo,relationships,reference,database,data,referential integrity,scan
|
|
6
6
|
Requires-Python: >=3.9,<4.0
|
|
@@ -8,10 +8,10 @@ import linkml_runtime
|
|
|
8
8
|
from refscan.lib.constants import console
|
|
9
9
|
from refscan.lib.helpers import (
|
|
10
10
|
connect_to_database,
|
|
11
|
+
get_collection_name_to_class_names_map,
|
|
11
12
|
get_collection_names_from_schema,
|
|
12
13
|
get_lowercase_key,
|
|
13
14
|
print_section_header,
|
|
14
|
-
get_names_of_classes_eligible_for_collection,
|
|
15
15
|
identify_references,
|
|
16
16
|
)
|
|
17
17
|
from refscan.lib.ViolationList import ViolationList
|
|
@@ -134,12 +134,7 @@ def scan(
|
|
|
134
134
|
console.print(f"Collections described by schema: {len(collection_names)}")
|
|
135
135
|
|
|
136
136
|
# For each collection, determine the names of the classes whose instances can be stored in that collection.
|
|
137
|
-
collection_name_to_class_names =
|
|
138
|
-
for collection_name in sorted(collection_names):
|
|
139
|
-
collection_name_to_class_names[collection_name] = get_names_of_classes_eligible_for_collection(
|
|
140
|
-
schema_view=schema_view,
|
|
141
|
-
collection_name=collection_name,
|
|
142
|
-
)
|
|
137
|
+
collection_name_to_class_names = get_collection_name_to_class_names_map(schema_view=schema_view)
|
|
143
138
|
|
|
144
139
|
# Identify the inter-document references that the schema allows to exist.
|
|
145
140
|
references = identify_references(
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from pathlib import Path
|
|
3
2
|
from typing import Union
|
|
4
3
|
import json
|
|
5
4
|
import base64
|
|
@@ -10,9 +9,9 @@ import linkml_runtime
|
|
|
10
9
|
from refscan import get_package_metadata
|
|
11
10
|
from refscan.lib.constants import console
|
|
12
11
|
from refscan.lib.helpers import (
|
|
12
|
+
get_collection_name_to_class_names_map,
|
|
13
13
|
print_section_header,
|
|
14
14
|
get_collection_names_from_schema,
|
|
15
|
-
get_names_of_classes_eligible_for_collection,
|
|
16
15
|
identify_references,
|
|
17
16
|
)
|
|
18
17
|
|
|
@@ -78,12 +77,7 @@ def graph(
|
|
|
78
77
|
console.print(f"Collections described by schema: {len(collection_names)}")
|
|
79
78
|
|
|
80
79
|
# For each collection, determine the names of the classes whose instances can be stored in that collection.
|
|
81
|
-
collection_name_to_class_names =
|
|
82
|
-
for collection_name in sorted(collection_names):
|
|
83
|
-
collection_name_to_class_names[collection_name] = get_names_of_classes_eligible_for_collection(
|
|
84
|
-
schema_view=schema_view,
|
|
85
|
-
collection_name=collection_name,
|
|
86
|
-
)
|
|
80
|
+
collection_name_to_class_names = get_collection_name_to_class_names_map(schema_view=schema_view)
|
|
87
81
|
|
|
88
82
|
# Identify the inter-document references that the schema allows to exist.
|
|
89
83
|
references = identify_references(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from typing import List, Optional, Dict
|
|
1
|
+
from typing import List, Optional, Dict, Tuple
|
|
2
2
|
|
|
3
3
|
from pymongo.database import Database
|
|
4
|
+
from pymongo.client_session import ClientSession
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class Finder:
|
|
@@ -10,6 +11,9 @@ class Finder:
|
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
13
|
def __init__(self, database: Database):
|
|
14
|
+
r"""
|
|
15
|
+
Initializes an instance of the `Finder` class. The instance is bound to the specified MongoDB database.
|
|
16
|
+
"""
|
|
13
17
|
self.db = database
|
|
14
18
|
|
|
15
19
|
# Initialize our cache of names of collections we most recently found referenced document `id`s in.
|
|
@@ -114,15 +118,20 @@ class Finder:
|
|
|
114
118
|
return self.cached_id_presence_by_collection[collection_name][document_id]
|
|
115
119
|
|
|
116
120
|
def check_whether_document_having_id_exists_among_collections(
|
|
117
|
-
self, document_id: str, collection_names: List[str]
|
|
121
|
+
self, document_id: str, collection_names: List[str], client_session: Optional[ClientSession] = None
|
|
118
122
|
) -> Optional[str]:
|
|
119
123
|
r"""
|
|
120
124
|
Checks whether any document in any of the specified collections has the specified value in its `id` field.
|
|
121
125
|
Returns the name of the first collection, if any, containing such a document. If none of the collections
|
|
122
126
|
contain such a document, the function returns `None`.
|
|
123
127
|
|
|
128
|
+
If a `pymongo.client_session.ClientSession` is specified, this function will access the database within
|
|
129
|
+
the context of that session. If a transaction is pending on that session, this feature can be used
|
|
130
|
+
to check whether the specified document _would_ exist if that transaction were to be committed.
|
|
131
|
+
|
|
124
132
|
References:
|
|
125
133
|
- https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.find_one
|
|
134
|
+
- https://pymongo.readthedocs.io/en/stable/api/pymongo/client_session.html#transactions
|
|
126
135
|
"""
|
|
127
136
|
|
|
128
137
|
# Initialize the name of the containing collection to `None`.
|
|
@@ -155,7 +164,8 @@ class Finder:
|
|
|
155
164
|
# If we find it in the collection in the database, record the collection name, update both (a) the cache
|
|
156
165
|
# of which documents we have found in which collections, and (b) the cache of names of collections where
|
|
157
166
|
# a referenced document was recently found, and skip searching additional collections.
|
|
158
|
-
|
|
167
|
+
collection = self.db.get_collection(collection_name)
|
|
168
|
+
if collection.find_one(query_filter, projection=["_id"], session=client_session) is not None:
|
|
159
169
|
name_of_collection_containing_target_document = collection_name
|
|
160
170
|
|
|
161
171
|
# Record that a document having this `id` is _present_ in this collection.
|
|
@@ -172,3 +182,48 @@ class Finder:
|
|
|
172
182
|
)
|
|
173
183
|
|
|
174
184
|
return name_of_collection_containing_target_document
|
|
185
|
+
|
|
186
|
+
def find_documents_having_type_and_value_in_field(
|
|
187
|
+
self,
|
|
188
|
+
collection_name: str,
|
|
189
|
+
type_and_field_name_tuples: List[Tuple[str, str]],
|
|
190
|
+
value: str,
|
|
191
|
+
client_session: Optional[ClientSession] = None,
|
|
192
|
+
) -> List[dict]:
|
|
193
|
+
r"""
|
|
194
|
+
Finds all documents residing in the specified collection that—for any of the specified
|
|
195
|
+
`type`-and-`field_name` combinations—have both (a) that `type` value and (b) the specified
|
|
196
|
+
`value` value in their `field_name` field. Returns a list of descriptors of those
|
|
197
|
+
documents (each descriptor consists of the document's `_id`, `id`, and `type`).
|
|
198
|
+
|
|
199
|
+
For example, if the `type_and_field_name_tuples` parameter contains the tuple
|
|
200
|
+
`("nmdc:Study", "part_of")`, and the `value` parameter is `"nmdc:sty-00-000001"`,
|
|
201
|
+
then this function will return all documents in the specified collection where both
|
|
202
|
+
(a) the document's `type` field contains the value `"nmdc:Study"` and
|
|
203
|
+
(b) the document's `part_of` field contains the value `"nmdc:sty-00-000001"`.
|
|
204
|
+
|
|
205
|
+
Note: `type_and_field_name_tuples` is a list of tuples, where the first item in each tuple is a
|
|
206
|
+
`type` value (e.g. "nmdc:Study") and the second item is a field name (e.g. "part_of").
|
|
207
|
+
|
|
208
|
+
If a `pymongo.client_session.ClientSession` is specified, this function will access the database within
|
|
209
|
+
the context of that session.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
# Build the query filter terms based on the `type`-and-`field_name` tuples.
|
|
213
|
+
query_filter_terms = []
|
|
214
|
+
for type_and_field_name_tuple in type_and_field_name_tuples:
|
|
215
|
+
type_value, field_name = type_and_field_name_tuple
|
|
216
|
+
query_filter_term = {"type": type_value, field_name: value}
|
|
217
|
+
query_filter_terms.append(query_filter_term)
|
|
218
|
+
|
|
219
|
+
# Define the overall query filter as a logical "OR" of the individual terms.
|
|
220
|
+
query_filter = {"$or": query_filter_terms}
|
|
221
|
+
|
|
222
|
+
# Only request essential data from the database (as a performance optimization).
|
|
223
|
+
projection = ["_id", "id", "type"]
|
|
224
|
+
|
|
225
|
+
documents = []
|
|
226
|
+
collection = self.db.get_collection(collection_name)
|
|
227
|
+
for document in collection.find(query_filter, projection=projection, session=client_session):
|
|
228
|
+
documents.append(document)
|
|
229
|
+
return documents
|
|
@@ -121,6 +121,35 @@ class ReferenceList(UserList):
|
|
|
121
121
|
|
|
122
122
|
return reference_field_names_by_source_class_name
|
|
123
123
|
|
|
124
|
+
def get_by_target_class_name(self, target_class_name: str) -> "ReferenceList":
|
|
125
|
+
r"""
|
|
126
|
+
Returns a new `ReferenceList` that contains only the references whose target class
|
|
127
|
+
has the specified name.
|
|
128
|
+
|
|
129
|
+
Maintainer note: The return type is written as a string to avoid the error:
|
|
130
|
+
> NameError: name 'ReferenceList' is not defined
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
filtered_references = ReferenceList()
|
|
134
|
+
for reference in self.data:
|
|
135
|
+
if reference.target_class_name == target_class_name:
|
|
136
|
+
filtered_references.append(reference)
|
|
137
|
+
return filtered_references
|
|
138
|
+
|
|
139
|
+
def group_by_source_collection_name(self) -> Dict[str, "ReferenceList"]:
|
|
140
|
+
r"""
|
|
141
|
+
Returns a dictionary that maps source collection names to `ReferenceList` instances
|
|
142
|
+
containing the references that have that source collection name.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
grouped_references: Dict[str, ReferenceList] = {}
|
|
146
|
+
for reference in self.data:
|
|
147
|
+
if reference.source_collection_name not in grouped_references:
|
|
148
|
+
grouped_references[reference.source_collection_name] = ReferenceList()
|
|
149
|
+
grouped_references[reference.source_collection_name].append(reference)
|
|
150
|
+
|
|
151
|
+
return grouped_references
|
|
152
|
+
|
|
124
153
|
def as_table(self) -> Table:
|
|
125
154
|
r"""
|
|
126
155
|
Returns the references as a `rich.Table` instance.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional, List
|
|
1
|
+
from typing import Optional, List, Dict
|
|
2
2
|
from functools import cache
|
|
3
3
|
|
|
4
4
|
from pymongo import MongoClient, timeout
|
|
@@ -30,6 +30,7 @@ def connect_to_database(mongo_uri: str, database_name: str, verbose: bool = True
|
|
|
30
30
|
return mongo_client
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
@cache # memoizes the decorated function
|
|
33
34
|
def get_collection_names_from_schema(schema_view: SchemaView) -> list[str]:
|
|
34
35
|
"""
|
|
35
36
|
Returns the names of the slots of the `Database` class that describe database collections.
|
|
@@ -53,6 +54,7 @@ def get_collection_names_from_schema(schema_view: SchemaView) -> list[str]:
|
|
|
53
54
|
return collection_names
|
|
54
55
|
|
|
55
56
|
|
|
57
|
+
@cache # memoizes the decorated function
|
|
56
58
|
def get_names_of_classes_eligible_for_collection(schema_view: SchemaView, collection_name: str) -> list[str]:
|
|
57
59
|
r"""
|
|
58
60
|
Returns a list of the names of the classes whose instances can be stored in the specified collection,
|
|
@@ -65,6 +67,30 @@ def get_names_of_classes_eligible_for_collection(schema_view: SchemaView, collec
|
|
|
65
67
|
return names_of_eligible_classes
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
@cache # memoizes the decorated function
|
|
71
|
+
def get_collection_name_to_class_names_map(
|
|
72
|
+
schema_view: SchemaView,
|
|
73
|
+
) -> Dict[str, List[str]]:
|
|
74
|
+
r"""
|
|
75
|
+
Returns a mapping of collection names to the names of the classes whose instances can be stored
|
|
76
|
+
in those collections, according to the specified `SchemaView`.
|
|
77
|
+
|
|
78
|
+
Example output:
|
|
79
|
+
```
|
|
80
|
+
{
|
|
81
|
+
"study_set": ["Study"],
|
|
82
|
+
"biosample_set": ["Biosample"],
|
|
83
|
+
...
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
"""
|
|
87
|
+
collection_name_to_class_names = {}
|
|
88
|
+
for collection_name in get_collection_names_from_schema(schema_view):
|
|
89
|
+
class_names = get_names_of_classes_eligible_for_collection(schema_view, collection_name)
|
|
90
|
+
collection_name_to_class_names[collection_name] = class_names
|
|
91
|
+
return collection_name_to_class_names
|
|
92
|
+
|
|
93
|
+
|
|
68
94
|
@cache # memoizes the decorated function
|
|
69
95
|
def translate_class_uri_into_schema_class_name(schema_view: SchemaView, class_uri: str) -> Optional[str]:
|
|
70
96
|
r"""
|
|
@@ -85,6 +111,22 @@ def translate_class_uri_into_schema_class_name(schema_view: SchemaView, class_ur
|
|
|
85
111
|
return schema_class_name
|
|
86
112
|
|
|
87
113
|
|
|
114
|
+
@cache # memoizes the decorated function
|
|
115
|
+
def translate_schema_class_name_into_class_uri(schema_view: SchemaView, schema_class_name: str) -> Optional[str]:
|
|
116
|
+
r"""
|
|
117
|
+
Returns the `class_uri` of the schema class having the specified name.
|
|
118
|
+
|
|
119
|
+
Example: "Biosample" (a schema class name) -> "nmdc:Biosample" (a `class_uri` value)
|
|
120
|
+
"""
|
|
121
|
+
class_uri = None
|
|
122
|
+
all_class_definitions_in_schema = schema_view.all_classes()
|
|
123
|
+
for class_name, class_definition in all_class_definitions_in_schema.items():
|
|
124
|
+
if class_name == schema_class_name:
|
|
125
|
+
class_uri = class_definition.class_uri
|
|
126
|
+
break
|
|
127
|
+
return class_uri
|
|
128
|
+
|
|
129
|
+
|
|
88
130
|
def derive_schema_class_name_from_document(schema_view: SchemaView, document: dict) -> Optional[str]:
|
|
89
131
|
r"""
|
|
90
132
|
Returns the name of the schema class, if any, of which the specified document claims to represent an instance.
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
from typing import List, Optional, Dict
|
|
1
|
+
from typing import List, Optional, Dict, Set, Tuple
|
|
2
2
|
|
|
3
3
|
from pymongo.database import Database
|
|
4
|
+
from pymongo.client_session import ClientSession
|
|
4
5
|
from rich.console import Console
|
|
5
6
|
from linkml_runtime import SchemaView
|
|
6
7
|
|
|
7
8
|
from refscan.lib.Finder import Finder
|
|
8
9
|
from refscan.lib.helpers import (
|
|
9
10
|
derive_schema_class_name_from_document,
|
|
11
|
+
translate_class_uri_into_schema_class_name,
|
|
12
|
+
translate_schema_class_name_into_class_uri,
|
|
10
13
|
init_progress_bar,
|
|
11
14
|
)
|
|
12
15
|
from refscan.lib.ReferenceList import ReferenceList
|
|
@@ -15,6 +18,95 @@ from refscan.lib.ViolationList import ViolationList
|
|
|
15
18
|
from refscan.lib.constants import console as default_console
|
|
16
19
|
|
|
17
20
|
|
|
21
|
+
def identify_referring_documents(
|
|
22
|
+
document: dict,
|
|
23
|
+
schema_view: SchemaView,
|
|
24
|
+
references: ReferenceList,
|
|
25
|
+
finder: Finder,
|
|
26
|
+
client_session: Optional[ClientSession] = None,
|
|
27
|
+
) -> List[dict]:
|
|
28
|
+
r"""
|
|
29
|
+
Identifies documents that reference the specified one.
|
|
30
|
+
|
|
31
|
+
Note: This function only searches collections and fields that the _schema_ says
|
|
32
|
+
can reference the specified document. If an arbitrary collection contains
|
|
33
|
+
a document that has a field that contains the `id` of the specified document
|
|
34
|
+
(e.g. the `foo_set` collection contains a document whose `nickname` field
|
|
35
|
+
contains `someIdentifier`), it is not necessarily the case that this function
|
|
36
|
+
will consider that to be a reference. It depends upon what the _schema_ says.
|
|
37
|
+
|
|
38
|
+
Note: This can be useful for determining whether the specified document can be safely deleted.
|
|
39
|
+
|
|
40
|
+
:param document: The document whose incoming references you want to identify
|
|
41
|
+
:param schema_view: A `SchemaView` bound to the schema
|
|
42
|
+
:param references: A `ReferenceList` derived from the schema
|
|
43
|
+
:param finder: A `Finder` bound to the database being scanned
|
|
44
|
+
:param client_session: A `pymongo.client_session.ClientSession` instance that, if specified, will be used when
|
|
45
|
+
searching for referring documents. If a transaction happens to be pending on that session,
|
|
46
|
+
the scan will effectively happen on the database as it _would_ exist if that transaction
|
|
47
|
+
were to be committed.
|
|
48
|
+
:return: A list of descriptors of documents that reference the specified document
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Initialize a list of descriptors of documents that reference the specified document.
|
|
52
|
+
referring_document_descriptors = []
|
|
53
|
+
|
|
54
|
+
# Get the specified document's `id` and derive its schema class name.
|
|
55
|
+
document_id = document["id"]
|
|
56
|
+
document_class_name = derive_schema_class_name_from_document(schema_view, document)
|
|
57
|
+
if document_class_name is None:
|
|
58
|
+
raise ValueError(f"Failed to identify schema class of document having `id`: {document_id}")
|
|
59
|
+
|
|
60
|
+
# Identify potential references to the specified document, then group them by their source collection names.
|
|
61
|
+
potential_references_to_document = references.get_by_target_class_name(document_class_name)
|
|
62
|
+
potential_references_by_source_collection = potential_references_to_document.group_by_source_collection_name()
|
|
63
|
+
|
|
64
|
+
# For each source collection name, check each of its potential references.
|
|
65
|
+
for collection_name, potential_references_in_collection in potential_references_by_source_collection.items():
|
|
66
|
+
# Make a list of all the distinct `class_uri`-and-`field_name` tuples.
|
|
67
|
+
#
|
|
68
|
+
# Note: Since a `Reference` doesn't have a `source_class_uri` attribute (but it does have a
|
|
69
|
+
# `source_class_name` attribute), we derive its `class_uri` from that `source_class_name` attribute.
|
|
70
|
+
#
|
|
71
|
+
class_uri_and_field_name_tuples: Set[Tuple[str, str]] = set()
|
|
72
|
+
for potential_reference in potential_references_in_collection:
|
|
73
|
+
class_uri = translate_schema_class_name_into_class_uri(
|
|
74
|
+
schema_view=schema_view, schema_class_name=potential_reference.source_class_name
|
|
75
|
+
)
|
|
76
|
+
if class_uri is None:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Failed to translate schema class name '{potential_reference.source_class_name}' into class_uri."
|
|
79
|
+
)
|
|
80
|
+
class_uri_and_field_name_tuple = (class_uri, potential_reference.source_field_name)
|
|
81
|
+
class_uri_and_field_name_tuples.add(class_uri_and_field_name_tuple)
|
|
82
|
+
|
|
83
|
+
# For each distinct combination of `class_uri` and `field_name`, check whether any documents in this
|
|
84
|
+
# collection both (a) have a `type` value matching that `class_uri` and (b) have the subject document's
|
|
85
|
+
# `id` value in the specified field.
|
|
86
|
+
referring_documents = finder.find_documents_having_type_and_value_in_field(
|
|
87
|
+
collection_name=collection_name,
|
|
88
|
+
type_and_field_name_tuples=list(class_uri_and_field_name_tuples),
|
|
89
|
+
value=document_id,
|
|
90
|
+
client_session=client_session,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# For each referring document, store a descriptor of it.
|
|
94
|
+
for referring_document in referring_documents:
|
|
95
|
+
source_class_name = translate_class_uri_into_schema_class_name(
|
|
96
|
+
schema_view=schema_view,
|
|
97
|
+
class_uri=referring_document["type"],
|
|
98
|
+
)
|
|
99
|
+
document_descriptor = dict(
|
|
100
|
+
source_collection_name=collection_name,
|
|
101
|
+
source_class_name=source_class_name,
|
|
102
|
+
source_document_object_id=referring_document["_id"],
|
|
103
|
+
source_document_id=referring_document["id"],
|
|
104
|
+
)
|
|
105
|
+
referring_document_descriptors.append(document_descriptor)
|
|
106
|
+
|
|
107
|
+
return referring_document_descriptors
|
|
108
|
+
|
|
109
|
+
|
|
18
110
|
def scan_outgoing_references(
|
|
19
111
|
document: dict,
|
|
20
112
|
schema_view: SchemaView,
|
|
@@ -24,6 +116,7 @@ def scan_outgoing_references(
|
|
|
24
116
|
finder: Finder,
|
|
25
117
|
collection_names: List[str],
|
|
26
118
|
source_collection_name: str,
|
|
119
|
+
client_session: Optional[ClientSession] = None,
|
|
27
120
|
user_wants_to_locate_misplaced_documents: bool = False,
|
|
28
121
|
) -> ViolationList:
|
|
29
122
|
r"""
|
|
@@ -35,12 +128,16 @@ def scan_outgoing_references(
|
|
|
35
128
|
:param reference_field_names_by_source_class_name: Dictionary mapping class names to lists of reference field names
|
|
36
129
|
:param references: A `ReferenceList` derived from the schema
|
|
37
130
|
:param finder: A `Finder` bound to the database being scanned
|
|
131
|
+
:param collection_names: List of collection names that are described by the schema
|
|
132
|
+
:param source_collection_name: Name of collection in which source document resides (this is included in the
|
|
133
|
+
violation report in an attempt to facilitate investigation of violations)
|
|
134
|
+
:param client_session: A `pymongo.client_session.ClientSession` instance that, if specified, will be used when
|
|
135
|
+
searching for referenced documents. If a transaction happens to be pending on that session,
|
|
136
|
+
the scan will effectively happen on the database as it _would_ exist if that transaction
|
|
137
|
+
were to be committed.
|
|
38
138
|
:param user_wants_to_locate_misplaced_documents: Whether the user wants the function to proceed to search illegal
|
|
39
139
|
collections after failing to find the referenced document among
|
|
40
140
|
all the legal collections
|
|
41
|
-
:param source_collection_name: Name of collection in which source document resides (this is included in the
|
|
42
|
-
violation report in an attempt to facilitate investigation of violations)
|
|
43
|
-
:param collection_names: List of collection names that are described by the schema
|
|
44
141
|
"""
|
|
45
142
|
|
|
46
143
|
# Initialize a list of violations.
|
|
@@ -77,7 +174,7 @@ def scan_outgoing_references(
|
|
|
77
174
|
for target_id in target_ids:
|
|
78
175
|
name_of_collection_containing_target_document = (
|
|
79
176
|
finder.check_whether_document_having_id_exists_among_collections(
|
|
80
|
-
collection_names=target_collection_names, document_id=target_id
|
|
177
|
+
collection_names=target_collection_names, document_id=target_id, client_session=client_session
|
|
81
178
|
)
|
|
82
179
|
)
|
|
83
180
|
if name_of_collection_containing_target_document is None:
|
|
@@ -88,7 +185,9 @@ def scan_outgoing_references(
|
|
|
88
185
|
names_of_ineligible_collections = list(set(collection_names) - set(target_collection_names))
|
|
89
186
|
name_of_collection_containing_target_document = (
|
|
90
187
|
finder.check_whether_document_having_id_exists_among_collections(
|
|
91
|
-
collection_names=names_of_ineligible_collections,
|
|
188
|
+
collection_names=names_of_ineligible_collections,
|
|
189
|
+
document_id=target_id,
|
|
190
|
+
client_session=client_session,
|
|
92
191
|
)
|
|
93
192
|
)
|
|
94
193
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|