Optional Introspect Utility
Creating a utility file to introspect your database is optional. This introspective functionality is designed to accommodate alterations in column structures and the addition of new tables. However, it's crucial to note that deleting a table essential to your code will result in the application breaking.
Create Utility file
Create a new python file 'utils.py' to handle introspection.
add the following code
'''Utilities module'''
import requests
import auth
import json
import os
PBAC_URL = "https://api.devii.io/roles_pbac"
OLD_DICT_FILE = 'old_dict.json' # This file will be used to store the tables and column data prior to introspection
When running the introspection for the first time the 'OLD_DICT_FILE' will be created and will report all of the current tables and columns as new.
Next, we will create functions that will load and save the OLD_DICT_FILE in preparation for comparison to changes after introspection is completed.
def load_old_dict():
if os.path.exists(OLD_DICT_FILE):
with open(OLD_DICT_FILE, "r") as file:
return json.load(file)
else:
return {}
def save_dict(old_dict):
with open(OLD_DICT_FILE, 'w') as file:
json.dump(old_dict, file)
The following functions will do the actual comparison between the data prior to introspection and after introspection.
def compare(old_dict, new_dict):
changes_detected = False
message = ''
# Check for added and removed tables:
# to detect new they must be whitelisted first and deleted tables must be removed from whitelist.
added_tables = set(new_dict.keys()) - set(old_dict.keys())
removed_tables = set(old_dict.keys()) - set(new_dict.keys())
if added_tables or removed_tables:
changes_detected = True
if added_tables:
message += f"Added tables: {added_tables}\n"
for added_table in added_tables:
message += f" Fields in {added_table}: {new_dict[added_table]}\n"
if removed_tables:
message += f"Removed tables: {removed_tables}\n"
for tabname, old_fields in old_dict.items():
new_fields = new_dict.get(tabname, [])
added_fields = set(new_fields) - set(old_fields)
removed_fields = set(old_fields) - set(new_fields)
if added_fields or removed_fields:
changes_detected = True
message += f"Changes in {tabname}: \n"
if added_fields:
message += f" Added fields: {added_fields}"
if removed_fields:
message += f" Removed fields: {removed_fields}"
if not changes_detected:
message += 'No changes detected'
return message
Finally, the Devii introspection function.
def devii_introspect():
old_dict = load_old_dict()
introspect = """{
Utility {introspect}
}
"""
util_introspect = {"query": introspect, "variables": {}}
response = requests.post(PBAC_URL, headers=auth.headers, json=util_introspect)
data = response.json()["data"]["Utility"]["introspect"]['json']['__schema']
qtype = [t for t in data['types'] if t['name']=='Query'][0]
tabnames = [field['name']for field in qtype['fields'] if field['name'] != 'Aggregates']
new_dict = {}
for tabname in tabnames:
fnames = next((x for x in data['types'] if x['name'] == tabname), None)
if fnames:
cnames = [field['name'] for field in fnames['fields']]
new_dict[tabname] = cnames
message = compare(old_dict, new_dict)
# Clear and update the old dictionary with new data
old_dict.clear()
old_dict.update(new_dict)
# Save the updated old dictionary to the file
save_dict(old_dict)
return message
devii_introspect()
Updates to other files in ToDo app
Note: all the functions outlined below have been included in the project and are outlined to facilitate removal if you would like.
app.py
The following function was added to the app.py file.
#The "GET" method is used here to ensure a response from GraphQL
@app.route("/introspection", methods=["GET"])
def introspect():
try:
# Run the devii_introspect function and get messages
messages = utils.devii_introspect()
# You can return additional data if needed
result = {"status": "success", "messages": messages}
except Exception as e:
# Handle exceptions if introspection fails
result = {"status": "error", "message": str(e)}
#to see the result in the console you can uncomment this
#print ("flask result: ", messages)
return result
home.html
The following code before </body>
at the end of the file.
<div class="introspect-button">
<button id="introspectionBtn" class="btn" >Run Introspection</button>
</div>
script.js
The following has been added at the end of the script.js file.
introspectionBtn.addEventListener("click", () => {
fetch("/introspection")
.then(response => response.json())
.then(data =>{alert(data.messages);
window.location.href = "/";
})
});