Skip to content

Instantly share code, notes, and snippets.

@josemartinez111
Last active November 27, 2022 20:39
Show Gist options
  • Save josemartinez111/610d51ab01acd246bad4f75c9ee0a5e5 to your computer and use it in GitHub Desktop.
Save josemartinez111/610d51ab01acd246bad4f75c9ee0a5e5 to your computer and use it in GitHub Desktop.
README for working with CRUD operations in MongoDB with pymongo

Module four CRUD assignment README

• A description of the purpose of the project:

The project’s purpose to demonstrate how to work with some of the crud functionality within MongoDB in relation with the Python programming language. In this particular initial project, we will only be using the first to letters of CRUD functions which to (CREATE) and (READ). Our testing will be done in Jupyter-Notebook which will showcase if our logic from our python script is functioning correctly and that our server is running within our python object AnimalShelter.


• A demonstration of how it works (its functional operations):

Let me brake down what tools I will be using. My IDE is DataSpell JetBrains which is an IDE specifically made for data science and jupyter-notebook integration right in the IDE. Which PyCharm does not. I began the project by creating a simple python project in a conda environment (Virtual Environment). With two files inside. A animal_shelter.py file and animals_db_script.ipynb file. I used the boilerplate code provided in the rubrics for this assignment and added or refactored the code to fit more of how I wanted to represent the AnimalShelter object. I am huge on using all programming languages with types and I think since python 3.7, you can implement types in python that though they do not work at compile time, do work at runtime. Which can make your python code much faster. I also added some utility functions.

  • An example of my custom types:
 # ========== custom utility types ==========
class AnimalData(TypedDict):
    age_upon_outcome: str
    animal_id: str
    animal_type: str
    breed: str
    color: str
    date_of_birth: str
    datetime: str
    monthyear: str
    name: str
    outcome_subtype: str
    outcome_type: str
    sex_upon_outcome: str
    location_lat: int
    location_long: int
    age_upon_outcome_in_weeks: int


class SearchType(TypedDict):
    animal_id: str


# ========== utility methods ==========
def pretty_print_json(response):
    formatted_response = json.dumps(response, indent=2)
    return formatted_response

Inside of the AnimalShelter object there is a lot of things going on. In our constructor we are using two parameters. Which is the username and password. I are also creating an object instance to our server, which when a new AnimalShelter instance is created, I can pass as arguments our username and password. I am also creating a database property that provides us our database of AAC. The fact that it is available everywhere in the AnimalShelter object is a bit confusing to me, since in other languages it would be scoped to the constructor and would only be available on a new object instance that is created.

def __init__(self, username: str, password: str):
        # Initializing the MongoClient. This helps to
        # access the MongoDB databases and collections.
        self.client = MongoClient(host=f'mongodb://{username}:{password}@localhost:27017/AAC')

        # where xxxx is your unique port number
        self.database = self.client['AAC']

The create method in the AnimalShelter object was mostly complete but it required that if a dictionary (json like object with keys & values) is passed as an argument to the method that the method will return True or else False. Which the logic is in the code example below:

# Complete this create method to implement the (CREATE) in CRUD.
   def create(self, data: list[AnimalData]):  # typed the data with a custom type (TypedDict)
       if data is None:
           raise Exception("Nothing to save, because data parameter is empty")

       inserted = self.database.animals.insert_one(data)  # data should be dictionary

       if inserted != 0:  # Will return True if insert is not == 0
           print(f"\n1. (CREATE) newly added animal: True")
           return True
       else:
           print(f"\n1. (CREATE) newly added animal: False")
           return False

The read method had search parameter that called the find and not the find_one method. Since they work differently. I then serialized the data so that it can be outputted in json format.

# Create method to implement the (READ) in CRUD.
def read(self, search: SearchType):  # SearchType = { animal_id: str }<- my search criteria
    if search is None:
        raise Exception("could not find search result...")
    if search:
        search_response = self.database.animals.find(search, {'_id': False})
        json_output = json.loads(json_util.dumps(search_response))

        if not json_output:
            print(f" Running..(READ) [No item found]: {json_output}")
        else:
            print(
                f"2. (READ) in CRUD by searching animal_id:"
                f" {pretty_print_json(json_output)}"
            )

        return json_output

I then created the update method which would call the update_one. The set part of the arguments passed was where I would pass my parameter of updated_data that I would pass as an argument when the data would update based on the filtered query. I then would call find for the updated_data, serialize it to be able to output it as json.

# (UPDATE) method in CRUD
def update(self, filtered_query, updated_data):
    if filtered_query is None or updated_data is None:
        raise Exception("Document failed to update")

    # updates the data based on the filtered queried type { 'key': 'value' }
    self.database.animals.update_one(filtered_query, {'$set': updated_data})
    # searches for the updated data
    output = self.database.animals.find(updated_data)
    # makes the data serializable
    json_output = json.loads(json_util.dumps(output))
    # prints the data in formatted json form
    print(
        f"3. (UPDATE) in CRUD by updating the animal_type:"
        f" {pretty_print_json(json_output)}"
    )
    
    return json_output

Finally, I created the delete CRUD method. I added a parameter to the delete method that queries the data based on the query. It could be anything within the collection. I then called delete on that query. Called find on the queried data, made it serializable, read the data one more time to make sure it was returning empty->[ ] and then outputting a formatted output. While also returning the json_output.

# (DELETE) method in CRUD
def delete(self, query):
    """Delete document fed from parameter"""

    if query is None:
        raise Exception("Document failed to delete")

    # deletes the data based on the query { 'key': 'value' }
    self.database.animals.delete_one(query)
    # searches for the queried data
    output = self.database.animals.find(query)
    # makes the data serializable
    json_output = json.loads(json_util.dumps(output))
    # runs read again that should verify that the data was deleted by returning ->[]
    search_for_deleted_data = self.read({'animal_id': 'A238675'})
    # a conditional variable that will return `True` if no data is found
    is_data_deleted: bool = search_for_deleted_data == []

    # formatted output
    # calling read to make sure it deleted
    print(
        f"4. After (READ) has ran.. Will return `True` if {query}"
        f" was deleted: {is_data_deleted}"
    )
    print(f"   Delete Complete for: {query}")
    print(f"   Should return an empty array: {json_output}")
    return json_output

• Testing the python script:

In terms of testing if my script worked I created a jupyter-notebook file an imported all of the api’s that would need in the test file to test my python code.

from animal_shelter import SearchType, AnimalShelter, AnimalData

I created some dummy data to be able to insert into the database and typed it with my custom AnimalData type, that I created in my python script. I also needed an instance of the AnimalShelter object in the notebook.

# ========== dummy data for testing ==========
DUMMY_DATA: AnimalData = {
    'age_upon_outcome': '3 year',
    'animal_id': 'A238675',
    'animal_type': 'Dog',
    'breed': 'German Shepperd',
    'color': 'Black',
    'date_of_birth': '2022-09-03',
    'datetime': '2022-09-03 11:43:00',
    'monthyear': '2022-09-0T11:43:00',
    'name': 'Jose',
    'outcome_subtype': 'Foster',
    'outcome_type': 'Adoption',
    'sex_upon_outcome': 'Male',
    'location_lat': 30.5202877527844,
    'location_long': -55.6574204375118,
    'age_upon_outcome_in_weeks': 15.0833333333333,
}

# creating an animal shelter object instance
animal_shelter = AnimalShelter(username='aacuser', password='alias111')

I then called the create method from my python script and then stored it in a variable created animal. The formatting is done in they script file. So, all I have to do is call the create method and it will print out in the correct format.

""" ••••••••• 
(CREATE) adding a new animal from dummy data to the animal’s collection 
••••••••• """
was_animal_created: bool = animal_shelter.create(DUMMY_DATA)

OUTPUT

Screenshot 2022-11-27 at 3 27 28 PM



I then queried the newly added data to the collection by way of a custom search query I created that I created a type for. Which would query based on the animal_id. I stored the call of the read method in a variable by the name of search_response. Which will print out a formatted version.

""" ••••••••• (READ) creating a search criteria to search for the new insert ••••••••• """
search: SearchType = {"animal_id": "A238675"}
search_response: Any = animal_shelter.read(search)

OUTPUT

Screenshot 2022-11-27 at 2 26 16 PM

I then tested the update method which takes two parameters as arguments. In this case I am using the filtered_query of the animal_id to find the animal I created with the create method. I updated the animal_type from Dog to Wolf.

""" ••••••••• updating the data inserted ••••••••• """
update_response: Any = animal_shelter.update(
    filtered_query={'animal_id': 'A238675'},
    updated_data={'animal_type': 'Wolf'}
)

OUTPUT

Screenshot 2022-11-27 at 3 14 50 PM

Testing the delete was straight forward. I just passed an argument querying the animal_id and the item was deleted.

""" ••••••••• deleting data ••••••••• """
data_to_delete_response = animal_shelter.delete({'animal_id': 'A238675'})

OUTPUT

Screenshot 2022-11-27 at 3 18 28 PM


A book that helped me immensely for this assignment was a book by the name of Mastering MongoDB 6.x Third Edition.

from bson import json_util
import json
from pymongo import MongoClient
from typing_extensions import TypedDict
# ========== custom utility types ==========
class AnimalData(TypedDict):
age_upon_outcome: str
animal_id: str
animal_type: str
breed: str
color: str
date_of_birth: str
datetime: str
monthyear: str
name: str
outcome_subtype: str
outcome_type: str
sex_upon_outcome: str
location_lat: int
location_long: int
age_upon_outcome_in_weeks: int
class SearchType(TypedDict):
animal_id: str
# ========== utility methods ==========
def pretty_print_json(response):
formatted_response = json.dumps(response, indent=2)
return formatted_response
# ===========================================================================
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username: str, password: str):
# Initializing the MongoClient. This helps to
# access the MongoDB databases and collections.
self.client = MongoClient(host=f'mongodb://{username}:{password}@localhost:27017/AAC')
# where xxxx is your unique port number
self.database = self.client['AAC']
# Complete this create method to implement the (CREATE) in CRUD.
def create(self, data: list[AnimalData]): # typed the data with a custom type (TypedDict)
if data is None:
raise Exception("Nothing to save, because data parameter is empty")
inserted = self.database.animals.insert_one(data) # data should be dictionary
if inserted != 0: # Will return True if insert is not == 0
print(f"\n1. (CREATE) newly added animal: True")
return True
else:
print(f"\n1. (CREATE) newly added animal: False")
return False
# Create method to implement the (READ) in CRUD.
def read(self, search: SearchType): # SearchType = { animal_id: str }<- my search criteria
if search is None:
raise Exception("could not find search result...")
if search:
search_response = self.database.animals.find(search, {'_id': False})
json_output = json.loads(json_util.dumps(search_response))
if not json_output:
print(f" Running..(READ) [No item found]: {json_output}")
else:
print(
f"2. (READ) in CRUD by searching animal_id:"
f" {pretty_print_json(json_output)}"
)
return json_output
# (UPDATE) method in CRUD
def update(self, filtered_query, updated_data):
if filtered_query is None or updated_data is None:
raise Exception("Document failed to update")
# updates the data based on the filtered queried type { 'key': 'value' }
self.database.animals.update_one(filtered_query, {'$set': updated_data})
# searches for the updated data
output = self.database.animals.find(updated_data)
# makes the data serializable
json_output = json.loads(json_util.dumps(output))
# prints the data in formatted json form
print(
f"3. (UPDATE) in CRUD by updating the animal_type:"
f" {pretty_print_json(json_output)}"
)
return json_output
# (DELETE) method in CRUD
def delete(self, query):
"""Delete document fed from parameter"""
if query is None:
raise Exception("Document failed to delete")
# deletes the data based on the query { 'key': 'value' }
self.database.animals.delete_one(query)
# searches for the queried data
output = self.database.animals.find(query)
# makes the data serializable
json_output = json.loads(json_util.dumps(output))
# runs read again that should verify that the data was deleted by returning ->[]
search_for_deleted_data = self.read({'animal_id': 'A238675'})
# a conditional variable that will return `True` if no data is found
is_data_deleted: bool = search_for_deleted_data == []
# formatted output
# calling read to make sure it deleted
print(
f"4. After (READ) has ran.. Will return `True` if {query}"
f" was deleted: {is_data_deleted}"
)
print(f" Delete Complete for: {query}")
print(f" Should return an empty array: {json_output}")
return json_output
from typing import Any
from animal_shelter import *
# ========== dummy data for testing ==========
DUMMY_DATA: AnimalData = {
'age_upon_outcome': '3 year',
'animal_id': 'A238675',
'animal_type': 'Dog',
'breed': 'German Shepperd',
'color': 'Black',
'date_of_birth': '2022-09-03',
'datetime': '2022-09-03 11:43:00',
'monthyear': '2022-09-0T11:43:00',
'name': 'Jose',
'outcome_subtype': 'Foster',
'outcome_type': 'Adoption',
'sex_upon_outcome': 'Male',
'location_lat': 30.5202877527844,
'location_long': -55.6574204375118,
'age_upon_outcome_in_weeks': 15.0833333333333,
}
# ============================================================
print('================== jose-martinez ==================')
""" ••••••••• creating an animal shelter object instance ••••••••• """
animal_shelter = AnimalShelter(username='aacuser', password='alias111')
""" ••••••••• (CREATE) adding a new animal from dummy data to the animals collection ••••••••• """
was_animal_created: bool = animal_shelter.create(DUMMY_DATA)
print()
""" ••••••••• (READ) creating a search criteria to search for the new insert ••••••••• """
search: SearchType = {"animal_id": "A238675"}
search_response: Any = animal_shelter.read(search)
print()
""" ••••••••• updating the data inserted ••••••••• """
update_response: Any = animal_shelter.update(
filtered_query={'animal_id': 'A238675'},
updated_data={'animal_type': 'Wolf'}
)
print()
""" ••••••••• deleting data ••••••••• """
data_to_delete_response = animal_shelter.delete({'animal_id': 'A238675'})
print('================== jose-martinez ==================')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment