Skip to content

Commit 69f6f3b

Browse files
Merge pull request #19 from agile-learning-institute/feature/add-enumerators-processing
Feature/add enumerators processing
2 parents 50443ce + 871096c commit 69f6f3b

File tree

6 files changed

+410
-153
lines changed

6 files changed

+410
-153
lines changed

Pipfile.lock

Lines changed: 114 additions & 102 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

REFERENCE.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,55 @@ The API processes collection configurations to manage MongoDB schemas, indexes,
7070
- Apply new Schema Validation
7171
- Load Test Data (optional)
7272

73+
## Enumerators Processing
74+
75+
The API processes enumerators from the `data/enumerators.json` file during the "process all" operation. This processing:
76+
77+
- Loads enumerator definitions from `INPUT_FOLDER/data/enumerators.json`
78+
- Validates the structure of each enumerator version
79+
- Up-serts enumerator documents into the database using the `ENUMERATORS_COLLECTION_NAME` collection
80+
- Uses the `version` field as the key for upsert operations
81+
82+
### Enumerators File Format
83+
84+
The `enumerators.json` file contains an array of enumerator version objects:
85+
86+
```json
87+
[
88+
{
89+
"version": 0,
90+
"name": "Enumerations",
91+
"status": "Deprecated",
92+
"enumerators": {}
93+
},
94+
{
95+
"version": 1,
96+
"name": "Enumerations",
97+
"status": "Active",
98+
"enumerators": {
99+
"default_status": {
100+
"active": "Not Deleted",
101+
"archived": "Soft Delete Indicator"
102+
},
103+
"media_type": {
104+
"movie": "A motion picture",
105+
"tv_show": "A television series"
106+
}
107+
}
108+
}
109+
]
110+
```
111+
112+
### Enumerators Processing Workflow
113+
114+
When processing all collections, the API:
115+
116+
1. **Processes Enumerators First**: Loads and up-serts all enumerator versions from `enumerators.json`
117+
2. **Processes Collections**: Then processes all configured collections as usual
118+
3. **Reports Results**: Includes enumerators processing results in the overall operation results
119+
120+
The enumerators processing is included in the "process all" operation results under the `"enumerators"` key, with the same operation result format as collection processing.
121+
73122
## Configuration Reference
74123

75124
The API is configured through environment variables.
@@ -80,6 +129,7 @@ The API is configured through environment variables.
80129
| `MONGO_DB_NAME` | MongoDB database name | `stage0` |
81130
| `MONGO_CONNECTION_STRING` | MongoDB connection string | `mongodb://root:example@localhost:27017/?tls=false&directConnection=true` |
82131
| `VERSION_COLLECTION_NAME`| MongoDB Version Collection name | `CollectionVersions` |
132+
| `ENUMERATORS_COLLECTION_NAME` | MongoDB Enumerators Collection name | `Enumerators` |
83133
| `INPUT_FOLDER` | Directory containing configurations | `/input` |
84134
| `LOAD_TEST_DATA` | Load Test data during processing | `false` |
85135
| `AUTO_PROCESS` | Process configurations on startup | `false` |

stage0_mongodb_api/managers/config_manager.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ def process_all_collections(self) -> Dict[str, List[Dict]]:
178178
results = {}
179179
any_collection_failed = False
180180

181+
# Process enumerators first
182+
enumerators_result = self._process_enumerators()
183+
results["enumerators"] = [enumerators_result]
184+
185+
# Check if enumerators processing failed
186+
if enumerators_result.get("status") == "error":
187+
any_collection_failed = True
188+
189+
# Process all collections
181190
for collection_name in self.collection_configs.keys():
182191
try:
183192
results[collection_name] = self.process_collection_versions(collection_name)
@@ -479,6 +488,60 @@ def _load_test_data(self, collection_name: str, test_data_file: str) -> Dict:
479488
"status": "error"
480489
}
481490

491+
def _process_enumerators(self) -> Dict:
492+
"""Process enumerators from the enumerators.json file.
493+
494+
Returns:
495+
Dict containing operation result in consistent format
496+
"""
497+
try:
498+
# Use the already-loaded enumerators from schema_manager
499+
enumerators = self.schema_manager.enumerators
500+
501+
# Process each enumerator version
502+
processed_count = 0
503+
504+
for document in enumerators:
505+
version = document.get("version")
506+
507+
# Upsert the document using version as the key
508+
result = self.mongo_io.upsert_document(
509+
self.config.ENUMERATORS_COLLECTION_NAME,
510+
{"version": version},
511+
document
512+
)
513+
514+
# upsert_document returns the document itself, so if we get a result, it succeeded
515+
if result and isinstance(result, dict):
516+
processed_count += 1
517+
else:
518+
raise Exception(f"Failed to upsert version {version}")
519+
520+
return {
521+
"operation": "process_enumerators",
522+
"collection": self.config.ENUMERATORS_COLLECTION_NAME,
523+
"message": f"Successfully processed {processed_count} enumerator versions",
524+
"details_type": "success",
525+
"details": {
526+
"processed_count": processed_count,
527+
"total_count": len(enumerators)
528+
},
529+
"status": "success"
530+
}
531+
532+
except Exception as e:
533+
return {
534+
"operation": "process_enumerators",
535+
"collection": self.config.ENUMERATORS_COLLECTION_NAME,
536+
"message": f"Error processing enumerators: {str(e)}",
537+
"details_type": "error",
538+
"details": {
539+
"error": str(e),
540+
"error_type": type(e).__name__
541+
},
542+
"status": "error"
543+
}
544+
482545
def _assert_no_errors(self, operations: List[Dict]) -> None:
483546
"""Check the last operation for errors and raise an exception if found.
484547

stage0_mongodb_api/services/collection_service.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def process_collections(token: Dict = None) -> List[Dict]:
106106
token: Authentication token for RBAC enforcement
107107
108108
Returns:
109-
List of processing results for each collection
109+
List of processing results for each collection and enumerators
110110
111111
Raises:
112112
CollectionProcessingError: If there are load or validation errors
@@ -122,22 +122,24 @@ def process_collections(token: Dict = None) -> List[Dict]:
122122
if validation_errors:
123123
raise CollectionProcessingError("collections", validation_errors)
124124

125+
# Use process_all_collections to include enumerators processing
126+
all_results = config_manager.process_all_collections()
127+
128+
# Convert the dictionary format to list format for API consistency
125129
results = []
126-
for collection_name, collection in config_manager.collection_configs.items():
127-
try:
128-
result = CollectionService.process_collection(collection_name, token)
129-
results.append(result)
130-
except CollectionNotFoundError:
131-
# Skip collections that don't exist
132-
continue
133-
except Exception as e:
134-
logger.error(f"Error processing collection {collection_name}: {str(e)}")
135-
results.append({
136-
"status": "error",
137-
"collection": collection_name,
138-
"message": str(e),
139-
"error": str(e)
140-
})
130+
for collection_name, operations in all_results.items():
131+
# Check if any operations have an error status
132+
has_errors = any(
133+
isinstance(op, dict) and op.get("status") == "error"
134+
for op in operations
135+
)
136+
137+
results.append({
138+
"collection": collection_name,
139+
"operations": operations,
140+
"status": "error" if has_errors else "success"
141+
})
142+
141143
return results
142144

143145
@staticmethod

tests/managers/test_config_manager.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,100 @@ def test_process_all_collections_structure(self):
213213
# Status should be the last property
214214
self.assertEqual(list(operation.keys())[-1], "status")
215215

216+
def test_process_enumerators_success(self):
217+
"""Test that _process_enumerators successfully processes enumerators."""
218+
test_case_dir = os.path.join(self.test_cases_dir, "small_sample")
219+
self.config.INPUT_FOLDER = test_case_dir
220+
self.config.ENUMERATORS_COLLECTION_NAME = "Enumerators"
221+
222+
# Mock schema_manager to return test enumerators
223+
mock_enumerators = [
224+
{"version": 0, "enumerators": {}},
225+
{"version": 1, "enumerators": {"default_status": ["active", "archived"]}}
226+
]
227+
228+
# Reset and mock MongoIO upsert_document to return the input document
229+
self.mock_mongoio_get_instance.return_value.upsert_document.reset_mock()
230+
def upsert_side_effect(collection, filter, document):
231+
return document
232+
self.mock_mongoio_get_instance.return_value.upsert_document.side_effect = upsert_side_effect
233+
234+
with patch('stage0_mongodb_api.managers.config_manager.SchemaManager') as mock_schema_manager_class:
235+
# Create a mock schema manager instance
236+
mock_schema_manager = MagicMock()
237+
mock_schema_manager.enumerators = mock_enumerators
238+
mock_schema_manager_class.return_value = mock_schema_manager
239+
240+
config_manager = ConfigManager()
241+
result = config_manager._process_enumerators()
242+
243+
# Test that we get the expected success structure
244+
self.assertEqual(result["operation"], "process_enumerators")
245+
self.assertEqual(result["collection"], "Enumerators")
246+
self.assertEqual(result["status"], "success")
247+
self.assertEqual(result["details_type"], "success")
248+
self.assertEqual(result["details"]["processed_count"], 2)
249+
self.assertEqual(result["details"]["total_count"], 2)
250+
251+
# Verify upsert_document was called for each enumerator
252+
self.assertEqual(
253+
self.mock_mongoio_get_instance.return_value.upsert_document.call_count, 2
254+
)
255+
256+
def test_process_all_collections_includes_enumerators(self):
257+
"""Test that process_all_collections includes enumerators processing."""
258+
test_case_dir = os.path.join(self.test_cases_dir, "small_sample")
259+
self.config.INPUT_FOLDER = test_case_dir
260+
self.config.ENUMERATORS_COLLECTION_NAME = "Enumerators"
261+
262+
# Mock schema_manager to return test enumerators
263+
mock_enumerators = [
264+
{"version": 0, "enumerators": {}},
265+
{"version": 1, "enumerators": {"default_status": ["active", "archived"]}}
266+
]
267+
268+
# Reset and mock MongoIO upsert_document to return the input document
269+
self.mock_mongoio_get_instance.return_value.upsert_document.reset_mock()
270+
def upsert_side_effect(collection, filter, document):
271+
return document
272+
self.mock_mongoio_get_instance.return_value.upsert_document.side_effect = upsert_side_effect
273+
274+
# Mock VersionManager.get_current_version to return a version that will be processed
275+
with patch('stage0_mongodb_api.managers.config_manager.VersionManager.get_current_version') as mock_get_version:
276+
mock_get_version.return_value = "simple.0.0.0.0"
277+
278+
# Mock all the manager operations to return success
279+
with patch('stage0_mongodb_api.managers.config_manager.SchemaManager') as mock_schema_manager, \
280+
patch('stage0_mongodb_api.managers.config_manager.IndexManager') as mock_index_manager, \
281+
patch('stage0_mongodb_api.managers.config_manager.MigrationManager') as mock_migration_manager, \
282+
patch('stage0_mongodb_api.managers.config_manager.VersionManager') as mock_version_manager:
283+
284+
# Set up mock schema manager with enumerators
285+
mock_schema_manager.return_value.enumerators = mock_enumerators
286+
mock_schema_manager.return_value.remove_schema.return_value = {
287+
"operation": "remove_schema", "collection": "simple", "status": "success"
288+
}
289+
mock_schema_manager.return_value.apply_schema.return_value = {
290+
"operation": "apply_schema", "collection": "simple", "schema": {}, "status": "success"
291+
}
292+
mock_version_manager.return_value.update_version.return_value = {
293+
"operation": "version_update", "collection": "simple", "version": "simple.1.0.0.1", "status": "success"
294+
}
295+
296+
config_manager = ConfigManager()
297+
result = config_manager.process_all_collections()
298+
299+
# Test that we get a dict with enumerators and collections
300+
self.assertIsInstance(result, dict)
301+
self.assertIn("enumerators", result)
302+
self.assertIn("simple", result)
303+
304+
# Test that enumerators processing is included
305+
enumerators_result = result["enumerators"]
306+
self.assertIsInstance(enumerators_result, list)
307+
self.assertEqual(len(enumerators_result), 2) # enumerators result + overall_status
308+
self.assertEqual(enumerators_result[0]["operation"], "process_enumerators")
309+
self.assertEqual(enumerators_result[0]["status"], "success")
310+
216311
if __name__ == '__main__':
217312
unittest.main()

0 commit comments

Comments
 (0)