From 89ef25dd1b2a0cde1bcc139817f2927297b8bc87 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 5 Nov 2024 22:30:30 -0800 Subject: [PATCH 01/47] added Get api --- app/clients/router.py | 35 +++++++++++++++++++++++++++++++++-- app/clients/schema.py | 13 +++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index f860c402..6f839341 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -1,15 +1,46 @@ -from fastapi import APIRouter +from fastapi import APIRouter, HTTPException from fastapi.responses import HTMLResponse +from typing import List from app.clients.service.logic import interpret_and_calculate -from app.clients.schema import PredictionInput +from app.clients.schema import PredictionInput, Client router = APIRouter(prefix="/clients", tags=["clients"]) +mock_clients_db = { + 1: Client( + id=1, + first_name="Amy", + last_name="Doe", + email="amy.doe@example.com", + date_of_birth="1995-04-23", + address="123 Main St, Springfield", + phone="123-456-7890" + ), + 2: Client( + id=2, + first_name="Bob", + last_name="Smith", + email="bob.smith@example.com", + date_of_birth="1999-08-17", + address="456 Elm St, Springfield", + phone="098-765-4321" + ), +} + @router.post("/predictions") async def predict(data: PredictionInput): print("HERE") print(data.model_dump()) return interpret_and_calculate(data.model_dump()) +@router.get("/clients/{id}", response_model=Client, summary="Retrieve client by ID") +async def get_client_by_id(id: int): + client = mock_clients_db.get(id) + if client is None: + raise HTTPException(status_code=404, detail="Client not found") + return client +@router.get("/clients", response_model=List[Client], summary="Retrieve all clients") +async def get_all_clients(): + return list(mock_clients_db.values()) diff --git a/app/clients/schema.py b/app/clients/schema.py index 6b56ad98..a9eb5b71 100644 --- a/app/clients/schema.py +++ b/app/clients/schema.py @@ -1,4 +1,7 @@ + from pydantic import BaseModel +from datetime import date +from typing import Optional class PredictionInput(BaseModel): age: int @@ -25,3 +28,13 @@ class PredictionInput(BaseModel): substance_use: str time_unemployed: int need_mental_health_support_bool: str + +class Client(BaseModel): + # ID will be auto-generated in a MangoDB database setup + id: int + first_name: str + last_name: str + email: str + date_of_birth: date + address: Optional[str] = None + phone: Optional[str] = None \ No newline at end of file From facc35c4ea4497ecd80e028d38e6ed7ac71cac51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Tue, 5 Nov 2024 22:56:39 -0800 Subject: [PATCH 02/47] Adding Delete APIs --- .lh/.lhignore | 6 ++ .lh/app/clients/router.py.json | 34 ++++++++++++ .lh/app/clients/service/logic.py.json | 18 ++++++ app/clients/router.py | 17 ++++++ app/clients/service/logic.py | 79 ++++++++++++++++----------- 5 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 .lh/.lhignore create mode 100644 .lh/app/clients/router.py.json create mode 100644 .lh/app/clients/service/logic.py.json diff --git a/.lh/.lhignore b/.lh/.lhignore new file mode 100644 index 00000000..1de51008 --- /dev/null +++ b/.lh/.lhignore @@ -0,0 +1,6 @@ +# list file to not track by the local-history extension. comment line starts with a '#' character +# each line describe a regular expression pattern (search for 'Javascript regex') +# it will relate to the workspace directory root. for example: +# '.*\.txt' ignores any file with 'txt' extension +# '/test/.*' ignores all the files under the 'test' directory +# '.*/test/.*' ignores all the files under any 'test' directory (even under sub-folders) diff --git a/.lh/app/clients/router.py.json b/.lh/app/clients/router.py.json new file mode 100644 index 00000000..40ac094f --- /dev/null +++ b/.lh/app/clients/router.py.json @@ -0,0 +1,34 @@ +{ + "sourceFile": "app/clients/router.py", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 4, + "patches": [ + { + "date": 1730875596606, + "content": "Index: \n===================================================================\n--- \n+++ \n" + }, + { + "date": 1730875608612, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -57,6 +57,6 @@\n return\n \n \n @router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n-async def get_all_clients():\n+async def delete_all_clients():\n return list(mock_clients_db.values())\n" + }, + { + "date": 1730875707491, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -50,12 +50,12 @@\n \n \n @router.delete(\"/clients/{id}\", response_model=None, summary=\"Delete client by ID\")\n async def delete_client_by_id(id: int):\n- client = mock_clients_db.get(id)\n+ client = mock_clients_db.pop(id, None)\n if client is None:\n raise HTTPException(status_code=404, detail=\"Client not found\")\n- return\n+ return {\"message\": f\"Client with ID {id} deleted successfully.\"}\n \n \n @router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n async def delete_all_clients():\n" + }, + { + "date": 1730875735473, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -58,5 +58,6 @@\n \n \n @router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n async def delete_all_clients():\n- return list(mock_clients_db.values())\n+ mock_clients_db.clear()\n+ return {\"message\": \"All clients deleted successfully.\"}\n" + }, + { + "date": 1730875916113, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -56,8 +56,8 @@\n raise HTTPException(status_code=404, detail=\"Client not found\")\n return {\"message\": f\"Client with ID {id} deleted successfully.\"}\n \n \n-@router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n+@router.delete(\"/clients\", response_model=None, summary=\"Delete all clients\")\n async def delete_all_clients():\n mock_clients_db.clear()\n return {\"message\": \"All clients deleted successfully.\"}\n" + } + ], + "date": 1730875596606, + "name": "Commit-0", + "content": "from fastapi import APIRouter, HTTPException\nfrom fastapi.responses import HTMLResponse\nfrom typing import List\n\nfrom app.clients.service.logic import interpret_and_calculate\nfrom app.clients.schema import PredictionInput, Client\n\nrouter = APIRouter(prefix=\"/clients\", tags=[\"clients\"])\n\nmock_clients_db = {\n 1: Client(\n id=1,\n first_name=\"Amy\",\n last_name=\"Doe\",\n email=\"amy.doe@example.com\",\n date_of_birth=\"1995-04-23\",\n address=\"123 Main St, Springfield\",\n phone=\"123-456-7890\"\n ),\n 2: Client(\n id=2,\n first_name=\"Bob\",\n last_name=\"Smith\",\n email=\"bob.smith@example.com\",\n date_of_birth=\"1999-08-17\",\n address=\"456 Elm St, Springfield\",\n phone=\"098-765-4321\"\n ),\n}\n\n\n@router.post(\"/predictions\")\nasync def predict(data: PredictionInput):\n print(\"HERE\")\n print(data.model_dump())\n return interpret_and_calculate(data.model_dump())\n\n\n@router.get(\"/clients/{id}\", response_model=Client, summary=\"Retrieve client by ID\")\nasync def get_client_by_id(id: int):\n client = mock_clients_db.get(id)\n if client is None:\n raise HTTPException(status_code=404, detail=\"Client not found\")\n return client\n\n\n@router.get(\"/clients\", response_model=List[Client], summary=\"Retrieve all clients\")\nasync def get_all_clients():\n return list(mock_clients_db.values())\n\n\n@router.delete(\"/clients/{id}\", response_model=None, summary=\"Delete client by ID\")\nasync def delete_client_by_id(id: int):\n client = mock_clients_db.get(id)\n if client is None:\n raise HTTPException(status_code=404, detail=\"Client not found\")\n return\n\n\n@router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\nasync def get_all_clients():\n return list(mock_clients_db.values())\n" + } + ] +} \ No newline at end of file diff --git a/.lh/app/clients/service/logic.py.json b/.lh/app/clients/service/logic.py.json new file mode 100644 index 00000000..aacd9c85 --- /dev/null +++ b/.lh/app/clients/service/logic.py.json @@ -0,0 +1,18 @@ +{ + "sourceFile": "app/clients/service/logic.py", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 0, + "patches": [ + { + "date": 1730875940736, + "content": "Index: \n===================================================================\n--- \n+++ \n" + } + ], + "date": 1730875940736, + "name": "Commit-0", + "content": "import os\nfrom typing import List\nimport pandas as pd\nimport json\nimport numpy as np\nimport pickle\nfrom itertools import combinations_with_replacement\nfrom itertools import product\n\ncolumn_intervention = [\n 'Life Stabilization',\n 'General Employment Assistance Services',\n 'Retention Services',\n 'Specialized Services',\n 'Employment-Related Financial Supports for Job Seekers and Employers',\n 'Employer Financial Supports',\n 'Enhanced Referrals for Skills Development'\n]\n\n# loads the model into logic\n\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nfilename = os.path.join(current_dir, 'model.pkl')\nmodel = pickle.load(open(filename, \"rb\"))\n\n\ndef clean_input_data(data):\n # translate input into wahtever we trained the model on, numerical data in a specific order\n columns = [\"age\", \"gender\", \"work_experience\", \"canada_workex\", \"dep_num\",\t\"canada_born\",\n \"citizen_status\",\t\"level_of_schooling\",\t\"fluent_english\",\t\"reading_english_scale\",\n \"speaking_english_scale\",\t\"writing_english_scale\",\t\"numeracy_scale\",\t\"computer_scale\",\n \"transportation_bool\",\t\"caregiver_bool\",\t\"housing\",\t\"income_source\",\t\"felony_bool\",\t\"attending_school\",\n \"currently_employed\",\t\"substance_use\",\t\"time_unemployed\",\t\"need_mental_health_support_bool\"]\n demographics = {\n 'age': data['age'],\n 'gender': data['gender'],\n 'work_experience': data['work_experience'],\n 'canada_workex': data['canada_workex'],\n 'dep_num': data['dep_num'],\n 'canada_born': data['canada_born'],\n 'citizen_status': data['citizen_status'],\n 'level_of_schooling': data['level_of_schooling'],\n 'fluent_english': data['fluent_english'],\n 'reading_english_scale': data['reading_english_scale'],\n 'speaking_english_scale': data['speaking_english_scale'],\n 'writing_english_scale': data['writing_english_scale'],\n 'numeracy_scale': data['numeracy_scale'],\n 'computer_scale': data['computer_scale'],\n 'transportation_bool': data['transportation_bool'],\n 'caregiver_bool': data['caregiver_bool'],\n 'housing': data['housing'],\n 'income_source': data['income_source'],\n 'felony_bool': data['felony_bool'],\n 'attending_school': data['attending_school'],\n 'currently_employed': data['currently_employed'],\n 'substance_use': data['substance_use'],\n 'time_unemployed': data['time_unemployed'],\n 'need_mental_health_support_bool': data['need_mental_health_support_bool']\n }\n output = []\n for column in columns:\n # default is None, and if you want to pass a value, can return any value\n data = demographics.get(column, None)\n if isinstance(data, str):\n data = convert_text(column, data)\n output.append(data)\n return output\n\n\ndef convert_text(column, data: str):\n # Convert text answers from front end into digits\n # TODO: ensure that categorical columns match the valid answers in FormNew.jsx (L131)\n categorical_cols_integers = [\n {\n \"\": 0,\n \"true\": 1,\n \"false\": 0,\n \"no\": 0,\n \"yes\": 1,\n \"No\": 0,\n \"Yes\": 1\n },\n {\n 'Grade 0-8': 1,\n 'Grade 9': 2,\n 'Grade 10': 3,\n 'Grade 11': 4,\n 'Grade 12 or equivalent': 5,\n 'OAC or Grade 13': 6,\n 'Some college': 7,\n 'Some university': 8,\n 'Some apprenticeship': 9,\n 'Certificate of Apprenticeship': 10,\n 'Journeyperson': 11,\n 'Certificate/Diploma': 12,\n 'Bachelor’s degree': 13,\n 'Post graduate': 14\n },\n {\n 'Renting-private': 1,\n 'Renting-subsidized': 2,\n 'Boarding or lodging': 3,\n 'Homeowner': 4,\n 'Living with family/friend': 5,\n 'Institution': 6,\n 'Temporary second residence': 7,\n 'Band-owned home': 8,\n 'Homeless or transient': 9,\n 'Emergency hostel': 10\n },\n {\n 'No Source of Income': 1,\n 'Employment Insurance': 2,\n 'Workplace Safety and Insurance Board': 3,\n 'Ontario Works applied or receiving': 4,\n 'Ontario Disability Support Program applied or receiving': 5,\n 'Dependent of someone receiving OW or ODSP': 6,\n 'Crown Ward': 7,\n 'Employment': 8,\n 'Self-Employment': 9,\n 'Other (specify)': 10\n }\n ]\n for category in categorical_cols_integers:\n print(f\"data: {data}\")\n print(f\"column: {column}\")\n if data in category:\n return category[data]\n\n if isinstance(data, str) and data.isnumeric():\n return int(data)\n\n return data\n\n# creates 128 possible combinations in order to run every possibility through model\n\n\ndef create_matrix(row):\n data = [row.copy() for _ in range(128)]\n perms = intervention_permutations(7)\n data = np.array(data)\n perms = np.array(perms)\n matrix = np.concatenate((data, perms), axis=1)\n return np.array(matrix)\n# create matrix of permutations of 1 and 0 of num length\n\n\ndef intervention_permutations(num):\n perms = list(product([0, 1], repeat=num))\n return np.array(perms)\n\n\ndef get_baseline_row(row):\n print(type(row))\n base_interventions = np.array([0]*7) # no interventions\n row = np.array(row)\n print(row)\n print(type(row))\n line = np.concatenate((row, base_interventions))\n return line\n\n\ndef intervention_row_to_names(row):\n names = []\n for i, value in enumerate(row):\n if value == 1:\n names.append(column_intervention[i])\n return names\n\n\ndef process_results(baseline, results):\n # Example:\n \"\"\"\n {\n baseline_probability: 80 #baseline percentage point with no interventions\n results: [\n (85, [A,B,C]) #new percentange with intervention combinations and list of intervention names\n (89, [B,C])\n (91, [D,E])\n ]\n }\n \"\"\"\n result_list = []\n for row in results:\n percent = row[-1]\n names = intervention_row_to_names(row)\n result_list.append((percent, names))\n\n output = {\n # if it's an array, want the value inside of the array\n \"baseline\": baseline[-1],\n \"interventions\": result_list,\n }\n return output\n\n\ndef interpret_and_calculate(data):\n raw_data = clean_input_data(data)\n baseline_row = get_baseline_row(raw_data)\n baseline_row = baseline_row.reshape(1, -1)\n print(\"BASELINE ROW IS\", baseline_row)\n intervention_rows = create_matrix(raw_data)\n baseline_prediction = model.predict(baseline_row)\n intervention_predictions = model.predict(intervention_rows)\n # want shape to be a vertical column, not a row\n intervention_predictions = intervention_predictions.reshape(-1, 1)\n result_matrix = np.concatenate(\n (intervention_rows, intervention_predictions), axis=1) # CHANGED AXIS\n\n # sort this matrix based on prediction\n # print(\"RESULT SAMPLE::\", result_matrix[:5])\n # take all rows and only last column, gives back list of indexes sorted\n result_order = result_matrix[:, -1].argsort()\n # indexing the matrix by the order\n result_matrix = result_matrix[result_order]\n\n # slice matrix to only top N results\n # -8 for interventions and prediction, want top 3, 3 combinations of intervention\n result_matrix = result_matrix[-3:, -8:]\n # post process results if needed ie make list of names for each row\n results = process_results(baseline_prediction, result_matrix)\n # build output dict\n print(f\"RESULTS: {results}\")\n return results\n\n\nif __name__ == \"__main__\":\n print(\"running\")\n data = {\n \"age\": \"23\",\n \"gender\": \"1\",\n \"work_experience\": \"1\",\n \"canada_workex\": \"1\",\n \"dep_num\": \"0\",\n \"canada_born\": \"1\",\n \"citizen_status\": \"2\",\n \"level_of_schooling\": \"2\",\n \"fluent_english\": \"3\",\n \"reading_english_scale\": \"2\",\n \"speaking_english_scale\": \"2\",\n \"writing_english_scale\": \"3\",\n \"numeracy_scale\": \"2\",\n \"computer_scale\": \"3\",\n \"transportation_bool\": \"2\",\n \"caregiver_bool\": \"1\",\n \"housing\": \"1\",\n \"income_source\": \"5\",\n \"felony_bool\": \"1\",\n \"attending_school\": \"0\",\n \"currently_employed\": \"1\",\n \"substance_use\": \"1\",\n \"time_unemployed\": \"1\",\n \"need_mental_health_support_bool\": \"1\"\n }\n # print(data)\n results = interpret_and_calculate(data)\n print(results)\n" + } + ] +} \ No newline at end of file diff --git a/app/clients/router.py b/app/clients/router.py index 6f839341..ef458229 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -28,12 +28,14 @@ ), } + @router.post("/predictions") async def predict(data: PredictionInput): print("HERE") print(data.model_dump()) return interpret_and_calculate(data.model_dump()) + @router.get("/clients/{id}", response_model=Client, summary="Retrieve client by ID") async def get_client_by_id(id: int): client = mock_clients_db.get(id) @@ -41,6 +43,21 @@ async def get_client_by_id(id: int): raise HTTPException(status_code=404, detail="Client not found") return client + @router.get("/clients", response_model=List[Client], summary="Retrieve all clients") async def get_all_clients(): return list(mock_clients_db.values()) + + +@router.delete("/clients/{id}", response_model=None, summary="Delete client by ID") +async def delete_client_by_id(id: int): + client = mock_clients_db.pop(id, None) + if client is None: + raise HTTPException(status_code=404, detail="Client not found") + return {"message": f"Client with ID {id} deleted successfully."} + + +@router.delete("/clients", response_model=None, summary="Delete all clients") +async def delete_all_clients(): + mock_clients_db.clear() + return {"message": "All clients deleted successfully."} diff --git a/app/clients/service/logic.py b/app/clients/service/logic.py index 0fd826a5..a7612cc1 100644 --- a/app/clients/service/logic.py +++ b/app/clients/service/logic.py @@ -1,3 +1,4 @@ +import os from typing import List import pandas as pd import json @@ -11,14 +12,13 @@ 'General Employment Assistance Services', 'Retention Services', 'Specialized Services', - 'Employment-Related Financial Supports for Job Seekers and Employers', + 'Employment-Related Financial Supports for Job Seekers and Employers', 'Employer Financial Supports', 'Enhanced Referrals for Skills Development' ] -#loads the model into logic +# loads the model into logic -import os current_dir = os.path.dirname(os.path.abspath(__file__)) filename = os.path.join(current_dir, 'model.pkl') @@ -26,11 +26,11 @@ def clean_input_data(data): - #translate input into wahtever we trained the model on, numerical data in a specific order - columns = ["age","gender","work_experience","canada_workex","dep_num", "canada_born", - "citizen_status", "level_of_schooling", "fluent_english", "reading_english_scale", - "speaking_english_scale", "writing_english_scale", "numeracy_scale", "computer_scale", - "transportation_bool", "caregiver_bool", "housing", "income_source", "felony_bool", "attending_school", + # translate input into wahtever we trained the model on, numerical data in a specific order + columns = ["age", "gender", "work_experience", "canada_workex", "dep_num", "canada_born", + "citizen_status", "level_of_schooling", "fluent_english", "reading_english_scale", + "speaking_english_scale", "writing_english_scale", "numeracy_scale", "computer_scale", + "transportation_bool", "caregiver_bool", "housing", "income_source", "felony_bool", "attending_school", "currently_employed", "substance_use", "time_unemployed", "need_mental_health_support_bool"] demographics = { 'age': data['age'], @@ -60,14 +60,15 @@ def clean_input_data(data): } output = [] for column in columns: - data = demographics.get(column, None) #default is None, and if you want to pass a value, can return any value + # default is None, and if you want to pass a value, can return any value + data = demographics.get(column, None) if isinstance(data, str): data = convert_text(column, data) output.append(data) return output -def convert_text(column, data:str): +def convert_text(column, data: str): # Convert text answers from front end into digits # TODO: ensure that categorical columns match the valid answers in FormNew.jsx (L131) categorical_cols_integers = [ @@ -132,37 +133,44 @@ def convert_text(column, data:str): return data -#creates 128 possible combinations in order to run every possibility through model +# creates 128 possible combinations in order to run every possibility through model + + def create_matrix(row): - data = [row.copy() for _ in range(128)] + data = [row.copy() for _ in range(128)] perms = intervention_permutations(7) data = np.array(data) perms = np.array(perms) - matrix = np.concatenate((data,perms), axis = 1) + matrix = np.concatenate((data, perms), axis=1) return np.array(matrix) -#create matrix of permutations of 1 and 0 of num length +# create matrix of permutations of 1 and 0 of num length + + def intervention_permutations(num): - perms = list(product([0,1],repeat=num)) + perms = list(product([0, 1], repeat=num)) return np.array(perms) + def get_baseline_row(row): print(type(row)) - base_interventions = np.array([0]*7) # no interventions + base_interventions = np.array([0]*7) # no interventions row = np.array(row) print(row) print(type(row)) - line = np.concatenate((row,base_interventions)) + line = np.concatenate((row, base_interventions)) return line + def intervention_row_to_names(row): names = [] for i, value in enumerate(row): - if value == 1: + if value == 1: names.append(column_intervention[i]) return names + def process_results(baseline, results): - ##Example: + # Example: """ { baseline_probability: 80 #baseline percentage point with no interventions @@ -173,42 +181,50 @@ def process_results(baseline, results): ] } """ - result_list= [] + result_list = [] for row in results: - percent = row[-1] + percent = row[-1] names = intervention_row_to_names(row) - result_list.append((percent,names)) + result_list.append((percent, names)) output = { - "baseline": baseline[-1], #if it's an array, want the value inside of the array + # if it's an array, want the value inside of the array + "baseline": baseline[-1], "interventions": result_list, } return output + def interpret_and_calculate(data): raw_data = clean_input_data(data) baseline_row = get_baseline_row(raw_data) baseline_row = baseline_row.reshape(1, -1) - print("BASELINE ROW IS",baseline_row) + print("BASELINE ROW IS", baseline_row) intervention_rows = create_matrix(raw_data) baseline_prediction = model.predict(baseline_row) intervention_predictions = model.predict(intervention_rows) - intervention_predictions = intervention_predictions.reshape(-1, 1) #want shape to be a vertical column, not a row - result_matrix = np.concatenate((intervention_rows,intervention_predictions), axis = 1) ##CHANGED AXIS - + # want shape to be a vertical column, not a row + intervention_predictions = intervention_predictions.reshape(-1, 1) + result_matrix = np.concatenate( + (intervention_rows, intervention_predictions), axis=1) # CHANGED AXIS + # sort this matrix based on prediction # print("RESULT SAMPLE::", result_matrix[:5]) - result_order = result_matrix[:,-1].argsort() #take all rows and only last column, gives back list of indexes sorted - result_matrix = result_matrix[result_order] #indexing the matrix by the order + # take all rows and only last column, gives back list of indexes sorted + result_order = result_matrix[:, -1].argsort() + # indexing the matrix by the order + result_matrix = result_matrix[result_order] # slice matrix to only top N results - result_matrix = result_matrix[-3:,-8:] #-8 for interventions and prediction, want top 3, 3 combinations of intervention + # -8 for interventions and prediction, want top 3, 3 combinations of intervention + result_matrix = result_matrix[-3:, -8:] # post process results if needed ie make list of names for each row - results = process_results(baseline_prediction,result_matrix) + results = process_results(baseline_prediction, result_matrix) # build output dict print(f"RESULTS: {results}") return results + if __name__ == "__main__": print("running") data = { @@ -240,4 +256,3 @@ def interpret_and_calculate(data): # print(data) results = interpret_and_calculate(data) print(results) - From 34bc075661c32399789f09554db7650797cea0bb Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Wed, 6 Nov 2024 13:42:52 -0800 Subject: [PATCH 03/47] Added Put API --- app/clients/router.py | 15 ++++++++++++++- app/clients/schema.py | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/clients/router.py b/app/clients/router.py index ef458229..0fe36401 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -3,7 +3,7 @@ from typing import List from app.clients.service.logic import interpret_and_calculate -from app.clients.schema import PredictionInput, Client +from app.clients.schema import PredictionInput, Client, ClientUpdate router = APIRouter(prefix="/clients", tags=["clients"]) @@ -61,3 +61,16 @@ async def delete_client_by_id(id: int): async def delete_all_clients(): mock_clients_db.clear() return {"message": "All clients deleted successfully."} + + +@router.put("/clients/{id}", response_model=Client, summary="Update client by ID") +async def update_client(id: int, client_data: ClientUpdate): + client = mock_clients_db.get(id) + if client is None: + raise HTTPException(status_code=404, detail="Client not found") + + updated_fields = client_data.model_dump(exclude_unset=True) + updated_client = client.model_copy(update=updated_fields) + mock_clients_db[id] = updated_client + + return updated_client \ No newline at end of file diff --git a/app/clients/schema.py b/app/clients/schema.py index a9eb5b71..00b15faa 100644 --- a/app/clients/schema.py +++ b/app/clients/schema.py @@ -37,4 +37,12 @@ class Client(BaseModel): email: str date_of_birth: date address: Optional[str] = None + phone: Optional[str] = None + +class ClientUpdate(BaseModel): + first_name: Optional[str] = None + last_name: Optional[str] = None + email: Optional[str] = None + date_of_birth: Optional[date] = None + address: Optional[str] = None phone: Optional[str] = None \ No newline at end of file From 9058a847dfcbd02be5b2363d0a5f40d0e7d60532 Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Wed, 6 Nov 2024 19:40:49 -0800 Subject: [PATCH 04/47] added post --- app/clients/router.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index 0fe36401..4dbb9bba 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -29,11 +29,15 @@ } -@router.post("/predictions") -async def predict(data: PredictionInput): - print("HERE") - print(data.model_dump()) - return interpret_and_calculate(data.model_dump()) +def generate_new_id(): + return max(mock_clients_db.keys(), default=0) + 1 + +@router.post("/create", response_model=Client, summary="Create a new client") +async def create_client(client_data: Client): + new_id = generate_new_id() + client_data.id = new_id # Set new client ID + mock_clients_db[new_id] = client_data + return client_data @router.get("/clients/{id}", response_model=Client, summary="Retrieve client by ID") From b71851c48cf9f8d0fe23b2eed9466bf712d845e7 Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Tue, 19 Nov 2024 20:26:01 -0800 Subject: [PATCH 05/47] Connected to MongoDB database and updated APIs. --- .gitignore | 1 + app/.env | 2 ++ app/clients/router.py | 75 ++++++++++++++++++++++++++++++++----------- app/clients/schema.py | 2 +- app/config.py | 10 ++++++ app/database.py | 6 ++++ 6 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 app/.env create mode 100644 app/config.py create mode 100644 app/database.py diff --git a/.gitignore b/.gitignore index 14d7fa72..281d9273 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .venv +venv/ .idea __pycache__ .DS_Store diff --git a/app/.env b/app/.env new file mode 100644 index 00000000..6fc15234 --- /dev/null +++ b/app/.env @@ -0,0 +1,2 @@ +MONGODB_URI = mongodb+srv://team:3Y9RHp9ZNRcV0COu@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 +MONGODB_NAME = Cluster0 diff --git a/app/clients/router.py b/app/clients/router.py index 4dbb9bba..b54c1e14 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -5,11 +5,16 @@ from app.clients.service.logic import interpret_and_calculate from app.clients.schema import PredictionInput, Client, ClientUpdate +from app.database import clients_collection +from bson import ObjectId + +from datetime import datetime, date + router = APIRouter(prefix="/clients", tags=["clients"]) mock_clients_db = { 1: Client( - id=1, + id="1", first_name="Amy", last_name="Doe", email="amy.doe@example.com", @@ -18,7 +23,7 @@ phone="123-456-7890" ), 2: Client( - id=2, + id="2", first_name="Bob", last_name="Smith", email="bob.smith@example.com", @@ -28,53 +33,85 @@ ), } - def generate_new_id(): return max(mock_clients_db.keys(), default=0) + 1 + @router.post("/create", response_model=Client, summary="Create a new client") async def create_client(client_data: Client): - new_id = generate_new_id() - client_data.id = new_id # Set new client ID - mock_clients_db[new_id] = client_data - return client_data + client_dict = client_data.dict(exclude={"id"}) + + # Convert any `datetime.date` fields to `datetime.datetime` + for key, value in client_dict.items(): + if isinstance(value, date): + client_dict[key] = datetime.combine(value, datetime.min.time()) + + result = await clients_collection.insert_one(client_dict) + client_dict["_id"] = result.inserted_id + return Client(id=str(client_dict["_id"]), **client_dict) @router.get("/clients/{id}", response_model=Client, summary="Retrieve client by ID") -async def get_client_by_id(id: int): - client = mock_clients_db.get(id) +async def get_client_by_id(id: str): + client = await clients_collection.find_one({"_id": ObjectId(id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") + # Convert ObjectId to string and return a formatted client object + client["id"] = str(client["_id"]) + del client["_id"] return client @router.get("/clients", response_model=List[Client], summary="Retrieve all clients") async def get_all_clients(): - return list(mock_clients_db.values()) + clients_cursor = await clients_collection.find().to_list(length=100) + # Convert _id to id and return the list + for client in clients_cursor: + client["id"] = str(client["_id"]) + del client["_id"] + + return clients_cursor @router.delete("/clients/{id}", response_model=None, summary="Delete client by ID") -async def delete_client_by_id(id: int): - client = mock_clients_db.pop(id, None) +async def delete_client_by_id(id: str): + if not ObjectId.is_valid(id): + raise HTTPException(status_code=400, detail="Invalid ObjectId format") + + client = await clients_collection.find_one({"_id": ObjectId(id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") + + delete_result = await clients_collection.delete_one({"_id": ObjectId(id)}) + if delete_result.deleted_count == 0: + raise HTTPException(status_code=404, detail="Client not found") + return {"message": f"Client with ID {id} deleted successfully."} @router.delete("/clients", response_model=None, summary="Delete all clients") async def delete_all_clients(): - mock_clients_db.clear() + await clients_collection.delete_many({}) return {"message": "All clients deleted successfully."} @router.put("/clients/{id}", response_model=Client, summary="Update client by ID") -async def update_client(id: int, client_data: ClientUpdate): - client = mock_clients_db.get(id) +async def update_client(id: str, client_data: ClientUpdate): + client = await clients_collection.find_one({"_id": ObjectId(id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") - updated_fields = client_data.model_dump(exclude_unset=True) - updated_client = client.model_copy(update=updated_fields) - mock_clients_db[id] = updated_client - + # Convert datetime.date to datetime.datetime for fields that are datetime.date + updated_fields = client_data.dict(exclude_unset=True) + # Ensure that any datetime.date objects are converted to datetime.datetime + for field, value in updated_fields.items(): + if isinstance(value, date): + updated_fields[field] = datetime.combine(value, datetime.min.time()) + + await clients_collection.update_one({"_id": ObjectId(id)}, {"$set": updated_fields}) + + updated_client = await clients_collection.find_one({"_id": ObjectId(id)}) + updated_client["id"] = str(updated_client["_id"]) + del updated_client["_id"] + return updated_client \ No newline at end of file diff --git a/app/clients/schema.py b/app/clients/schema.py index 00b15faa..0697df8b 100644 --- a/app/clients/schema.py +++ b/app/clients/schema.py @@ -31,7 +31,7 @@ class PredictionInput(BaseModel): class Client(BaseModel): # ID will be auto-generated in a MangoDB database setup - id: int + id: str first_name: str last_name: str email: str diff --git a/app/config.py b/app/config.py new file mode 100644 index 00000000..65fa7103 --- /dev/null +++ b/app/config.py @@ -0,0 +1,10 @@ +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + MONGODB_URI: str + MONGODB_NAME: str + + class Config: + env_file = ".env" + +settings = Settings() diff --git a/app/database.py b/app/database.py new file mode 100644 index 00000000..0436ad56 --- /dev/null +++ b/app/database.py @@ -0,0 +1,6 @@ +from motor.motor_asyncio import AsyncIOMotorClient +from app.config import settings + +client = AsyncIOMotorClient(settings.MONGODB_URI) +database = client[settings.MONGODB_NAME] +clients_collection = database.get_collection("clients") From f5ab3420975b4616e051004dad6a04526fa36f4b Mon Sep 17 00:00:00 2001 From: joycelalalayaa Date: Sat, 23 Nov 2024 09:38:39 -0800 Subject: [PATCH 06/47] Create pylint action --- .github/workflows/pylint.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/pylint.yml diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 00000000..637651cf --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,24 @@ +name: Pylint + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint ./app; # pylint $(git ls-files '*.py') + From fcaa962fbc1686575fd2dacd3cabe00d2d2c7c48 Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Sat, 23 Nov 2024 11:17:05 -0800 Subject: [PATCH 07/47] Improved import order --- app/clients/service/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/clients/service/model.py b/app/clients/service/model.py index 51369ac3..c6cc6bb4 100644 --- a/app/clients/service/model.py +++ b/app/clients/service/model.py @@ -1,5 +1,5 @@ -import pandas as pd import json +import pandas as pd import numpy as np import pickle from sklearn.model_selection import train_test_split From f73e81b1bf672c91f4553cc0671bcb8473a7770f Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Sat, 23 Nov 2024 11:59:23 -0800 Subject: [PATCH 08/47] Added runServer.sh + got rid of some unused code --- app/clients/service/model.py | 97 +++++++++++++++--------------------- runServer.sh | 7 +++ 2 files changed, 48 insertions(+), 56 deletions(-) create mode 100755 runServer.sh diff --git a/app/clients/service/model.py b/app/clients/service/model.py index c6cc6bb4..699b29ac 100644 --- a/app/clients/service/model.py +++ b/app/clients/service/model.py @@ -1,39 +1,45 @@ -import json +""" +This module prepares and trains a machine learning model using the dataset, +and saves the trained model for future use. +""" + import pandas as pd -import numpy as np -import pickle -from sklearn.model_selection import train_test_split -from sklearn.ensemble import RandomForestRegressor def prepare_models(): + """ + Prepares and trains a RandomForestRegressor model using the dataset. + """ # Load dataset and define the features and labels - backendCode = pd.read_csv('data_commontool.csv') + backend_code = pd.read_csv('data_commontool.csv') # Variable renamed to snake_case # Define categorical columns and interventions - categorical_cols = ['age', - 'gender', #bool - 'work_experience', #years of work experience - 'canada_workex',#years of work experience in canada - 'dep_num', #number of dependents - 'canada_born', #born in canada - 'citizen_status', #citizen status - 'level_of_schooling', #highest level achieved (1-14) - 'fluent_english', #english level fluency, scale (1-10) - 'reading_english_scale', #reading scale (1-10) - 'speaking_english_scale', #speaking level comfort (1-10) - 'writing_english_scale', #writing scale (1-10) - 'numeracy_scale', #numeracy scale (1-10) - 'computer_scale', #computer use scale (1-10) - 'transportation_bool', #need transportation support (bool) - 'caregiver_bool', #is a primary care giver bool - 'housing', #housing situation 1-10 - 'income_source', #source of income 1-10 - 'felony_bool', #has a felony bool - 'attending_school', #currently a student bool - 'currently_employed', #currently employed bool - 'substance_use', #disorder, bool - 'time_unemployed', #number of years unemployed - 'need_mental_health_support_bool'] #need support + categorical_cols = [ + 'age', + 'gender', # bool + 'work_experience', # years of work experience + 'canada_workex', # years of work experience in Canada + 'dep_num', # number of dependents + 'canada_born', # born in Canada + 'citizen_status', # citizen status + 'level_of_schooling', # highest level achieved (1-14) + 'fluent_english', # English level fluency, scale (1-10) + 'reading_english_scale', # reading scale (1-10) + 'speaking_english_scale', # speaking level comfort (1-10) + 'writing_english_scale', # writing scale (1-10) + 'numeracy_scale', # numeracy scale (1-10) + 'computer_scale', # computer use scale (1-10) + 'transportation_bool', # need transportation support (bool) + 'caregiver_bool', # is a primary caregiver (bool) + 'housing', # housing situation (1-10) + 'income_source', # source of income (1-10) + 'felony_bool', # has a felony (bool) + 'attending_school', # currently a student (bool) + 'currently_employed', # currently employed (bool) + 'substance_use', # disorder (bool) + 'time_unemployed', # number of years unemployed + 'need_mental_health_support_bool', # need support + ] + interventions = [ 'employment_assistance', 'life_stabilization', @@ -41,32 +47,11 @@ def prepare_models(): 'specialized_services', 'employment_related_financial_supports', 'employer_financial_supports', - 'enhanced_referrals' + 'enhanced_referrals', ] categorical_cols.extend(interventions) - # Prepare training data - X_categorical_baseline = backendCode[categorical_cols] - y_baseline = backendCode['success_rate'] - X_categorical_baseline = np.array(X_categorical_baseline) - y_baseline = np.array(y_baseline) - X_train_baseline, X_test_baseline, y_train_baseline, y_test_baseline = train_test_split( - X_categorical_baseline, y_baseline, test_size=0.2, random_state=42) - - rf_model_baseline = RandomForestRegressor(n_estimators=100, random_state=42) - rf_model_baseline.fit(X_train_baseline, y_train_baseline) - # Example: Predicting on the test set - baseline_predictions = rf_model_baseline.predict(X_test_baseline) - - - return rf_model_baseline - -def main(): - print("Start model.") - model = prepare_models() - - pickle.dump(model, open("model.pkl", "wb")) #saves model to the file name input, write binary - model = pickle.load(open("model.pkl", "rb")) #read binary - -if __name__ == "__main__": - main() + # Prepare training data + x_categorical_baseline = backend_code[categorical_cols] # Variable renamed to snake_case + y_baseline = backend_code['success_rate'] + x_categorical_baseline diff --git a/runServer.sh b/runServer.sh new file mode 100755 index 00000000..ce403bc6 --- /dev/null +++ b/runServer.sh @@ -0,0 +1,7 @@ +python3 -m venv venv +source venv/bin/activate +export PYTHONPATH="$(pwd):$PYTHONPATH" +pip install uvicorn +pip install -r requirements.txt +cd app +uvicorn main:app --reload \ No newline at end of file From 5838c2472246fc0c7eed10d263f09dcbadd24ee9 Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Sat, 23 Nov 2024 18:37:09 -0800 Subject: [PATCH 09/47] made some updated to router.py --- app/clients/router.py | 99 +++++++++++++++++++++++++++--------- app/clients/service/model.py | 1 - requirements.txt | 1 + 3 files changed, 77 insertions(+), 24 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index b54c1e14..afc3a2bb 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -1,14 +1,15 @@ -from fastapi import APIRouter, HTTPException -from fastapi.responses import HTMLResponse -from typing import List +""" +This module handles client-related API routes for the application. +""" -from app.clients.service.logic import interpret_and_calculate -from app.clients.schema import PredictionInput, Client, ClientUpdate +from datetime import datetime, date +from typing import List -from app.database import clients_collection from bson import ObjectId +from fastapi import APIRouter, HTTPException -from datetime import datetime, date +from app.clients.schema import Client, ClientUpdate +from app.database import clients_collection router = APIRouter(prefix="/clients", tags=["clients"]) @@ -34,11 +35,23 @@ } def generate_new_id(): + """ + Generate a new unique ID for a client. + """ return max(mock_clients_db.keys(), default=0) + 1 @router.post("/create", response_model=Client, summary="Create a new client") async def create_client(client_data: Client): + """ + Create a new client in the database. + + Args: + client_data (Client): The client data to create. + + Returns: + Client: The created client object. + """ client_dict = client_data.dict(exclude={"id"}) # Convert any `datetime.date` fields to `datetime.datetime` @@ -51,11 +64,21 @@ async def create_client(client_data: Client): return Client(id=str(client_dict["_id"]), **client_dict) -@router.get("/clients/{id}", response_model=Client, summary="Retrieve client by ID") -async def get_client_by_id(id: str): - client = await clients_collection.find_one({"_id": ObjectId(id)}) +@router.get("/clients/{client_id}", response_model=Client, summary="Retrieve client by ID") +async def get_client_by_id(client_id: str): + """ + Retrieve a client by their ID. + + Args: + client_id (str): The ID of the client to retrieve. + + Returns: + Client: The retrieved client object. + """ + client = await clients_collection.find_one({"_id": ObjectId(client_id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") + # Convert ObjectId to string and return a formatted client object client["id"] = str(client["_id"]) del client["_id"] @@ -64,6 +87,12 @@ async def get_client_by_id(id: str): @router.get("/clients", response_model=List[Client], summary="Retrieve all clients") async def get_all_clients(): + """ + Retrieve all clients in the database. + + Returns: + List[Client]: A list of all clients. + """ clients_cursor = await clients_collection.find().to_list(length=100) # Convert _id to id and return the list for client in clients_cursor: @@ -73,45 +102,69 @@ async def get_all_clients(): return clients_cursor -@router.delete("/clients/{id}", response_model=None, summary="Delete client by ID") -async def delete_client_by_id(id: str): - if not ObjectId.is_valid(id): +@router.delete("/clients/{client_id}", response_model=None, summary="Delete client by ID") +async def delete_client_by_id(client_id: str): + """ + Delete a client by their ID. + + Args: + client_id (str): The ID of the client to delete. + + Returns: + dict: A message indicating successful deletion. + """ + if not ObjectId.is_valid(client_id): raise HTTPException(status_code=400, detail="Invalid ObjectId format") - client = await clients_collection.find_one({"_id": ObjectId(id)}) + client = await clients_collection.find_one({"_id": ObjectId(client_id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") - delete_result = await clients_collection.delete_one({"_id": ObjectId(id)}) + delete_result = await clients_collection.delete_one({"_id": ObjectId(client_id)}) if delete_result.deleted_count == 0: raise HTTPException(status_code=404, detail="Client not found") - return {"message": f"Client with ID {id} deleted successfully."} + return {"message": f"Client with ID {client_id} deleted successfully."} @router.delete("/clients", response_model=None, summary="Delete all clients") async def delete_all_clients(): + """ + Delete all clients in the database. + + Returns: + dict: A message indicating successful deletion of all clients. + """ await clients_collection.delete_many({}) return {"message": "All clients deleted successfully."} -@router.put("/clients/{id}", response_model=Client, summary="Update client by ID") -async def update_client(id: str, client_data: ClientUpdate): - client = await clients_collection.find_one({"_id": ObjectId(id)}) +@router.put("/clients/{client_id}", response_model=Client, summary="Update client by ID") +async def update_client(client_id: str, client_data: ClientUpdate): + """ + Update a client's information by their ID. + + Args: + client_id (str): The ID of the client to update. + client_data (ClientUpdate): The updated client data. + + Returns: + Client: The updated client object. + """ + client = await clients_collection.find_one({"_id": ObjectId(client_id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") # Convert datetime.date to datetime.datetime for fields that are datetime.date updated_fields = client_data.dict(exclude_unset=True) - # Ensure that any datetime.date objects are converted to datetime.datetime for field, value in updated_fields.items(): if isinstance(value, date): updated_fields[field] = datetime.combine(value, datetime.min.time()) - await clients_collection.update_one({"_id": ObjectId(id)}, {"$set": updated_fields}) + await clients_collection.update_one({"_id": ObjectId(client_id)}, {"$set": updated_fields}) - updated_client = await clients_collection.find_one({"_id": ObjectId(id)}) + updated_client = await clients_collection.find_one({"_id": ObjectId(client_id)}) updated_client["id"] = str(updated_client["_id"]) del updated_client["_id"] - return updated_client \ No newline at end of file + return updated_client diff --git a/app/clients/service/model.py b/app/clients/service/model.py index 699b29ac..6ded7829 100644 --- a/app/clients/service/model.py +++ b/app/clients/service/model.py @@ -53,5 +53,4 @@ def prepare_models(): # Prepare training data x_categorical_baseline = backend_code[categorical_cols] # Variable renamed to snake_case - y_baseline = backend_code['success_rate'] x_categorical_baseline diff --git a/requirements.txt b/requirements.txt index 1ccf75b7..1220ef81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -140,3 +140,4 @@ webencodings==0.5.1 websocket-client==1.5.1 websockets==11.0.3 widgetsnbextension==4.0.7 +motor==3.6.0 \ No newline at end of file From f3730bff96a6910e97b34ee2ca41116ff28cb6ac Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Sat, 23 Nov 2024 19:08:33 -0800 Subject: [PATCH 10/47] made some updated to logic.py --- app/clients/router.py | 11 +- app/clients/service/logic.py | 299 +++++++++++++---------------------- app/main.py | 4 +- requirements.txt | 2 +- 4 files changed, 113 insertions(+), 203 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index afc3a2bb..a3ce0edb 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -52,18 +52,14 @@ async def create_client(client_data: Client): Returns: Client: The created client object. """ - client_dict = client_data.dict(exclude={"id"}) - - # Convert any `datetime.date` fields to `datetime.datetime` + client_dict = client_data.dict(exclude={"id"}) for key, value in client_dict.items(): if isinstance(value, date): client_dict[key] = datetime.combine(value, datetime.min.time()) - - result = await clients_collection.insert_one(client_dict) + result = await clients_collection.insert_one(client_dict) client_dict["_id"] = result.inserted_id return Client(id=str(client_dict["_id"]), **client_dict) - @router.get("/clients/{client_id}", response_model=Client, summary="Retrieve client by ID") async def get_client_by_id(client_id: str): """ @@ -78,7 +74,6 @@ async def get_client_by_id(client_id: str): client = await clients_collection.find_one({"_id": ObjectId(client_id)}) if client is None: raise HTTPException(status_code=404, detail="Client not found") - # Convert ObjectId to string and return a formatted client object client["id"] = str(client["_id"]) del client["_id"] @@ -125,8 +120,6 @@ async def delete_client_by_id(client_id: str): raise HTTPException(status_code=404, detail="Client not found") return {"message": f"Client with ID {client_id} deleted successfully."} - - @router.delete("/clients", response_model=None, summary="Delete all clients") async def delete_all_clients(): """ diff --git a/app/clients/service/logic.py b/app/clients/service/logic.py index a7612cc1..11e52a06 100644 --- a/app/clients/service/logic.py +++ b/app/clients/service/logic.py @@ -1,12 +1,15 @@ +""" +Logic for processing user data and calculating interventions. +Uses a trained model to predict outcomes based on user input. +""" + import os -from typing import List -import pandas as pd -import json -import numpy as np import pickle -from itertools import combinations_with_replacement from itertools import product +import numpy as np # Ensure numpy is installed via `pip install numpy` + +# Columns representing possible interventions column_intervention = [ 'Life Stabilization', 'General Employment Assistance Services', @@ -17,242 +20,158 @@ 'Enhanced Referrals for Skills Development' ] -# loads the model into logic - - +# Load the trained model current_dir = os.path.dirname(os.path.abspath(__file__)) filename = os.path.join(current_dir, 'model.pkl') -model = pickle.load(open(filename, "rb")) +with open(filename, "rb") as file: + model = pickle.load(file) def clean_input_data(data): - # translate input into wahtever we trained the model on, numerical data in a specific order - columns = ["age", "gender", "work_experience", "canada_workex", "dep_num", "canada_born", - "citizen_status", "level_of_schooling", "fluent_english", "reading_english_scale", - "speaking_english_scale", "writing_english_scale", "numeracy_scale", "computer_scale", - "transportation_bool", "caregiver_bool", "housing", "income_source", "felony_bool", "attending_school", - "currently_employed", "substance_use", "time_unemployed", "need_mental_health_support_bool"] - demographics = { - 'age': data['age'], - 'gender': data['gender'], - 'work_experience': data['work_experience'], - 'canada_workex': data['canada_workex'], - 'dep_num': data['dep_num'], - 'canada_born': data['canada_born'], - 'citizen_status': data['citizen_status'], - 'level_of_schooling': data['level_of_schooling'], - 'fluent_english': data['fluent_english'], - 'reading_english_scale': data['reading_english_scale'], - 'speaking_english_scale': data['speaking_english_scale'], - 'writing_english_scale': data['writing_english_scale'], - 'numeracy_scale': data['numeracy_scale'], - 'computer_scale': data['computer_scale'], - 'transportation_bool': data['transportation_bool'], - 'caregiver_bool': data['caregiver_bool'], - 'housing': data['housing'], - 'income_source': data['income_source'], - 'felony_bool': data['felony_bool'], - 'attending_school': data['attending_school'], - 'currently_employed': data['currently_employed'], - 'substance_use': data['substance_use'], - 'time_unemployed': data['time_unemployed'], - 'need_mental_health_support_bool': data['need_mental_health_support_bool'] - } + """ + Translate input data into the format required by the trained model. + Args: + data (dict): Input data from the frontend. + Returns: + list: Transformed numerical data in a specific order. + """ + columns = [ + "age", "gender", "work_experience", "canada_workex", "dep_num", "canada_born", + "citizen_status", "level_of_schooling", "fluent_english", "reading_english_scale", + "speaking_english_scale", "writing_english_scale", "numeracy_scale", "computer_scale", + "transportation_bool", "caregiver_bool", "housing", "income_source", "felony_bool", + "attending_school", "currently_employed", "substance_use", "time_unemployed", + "need_mental_health_support_bool" + ] + demographics = {key: data[key] for key in columns if key in data} output = [] for column in columns: - # default is None, and if you want to pass a value, can return any value - data = demographics.get(column, None) - if isinstance(data, str): - data = convert_text(column, data) - output.append(data) + value = demographics.get(column, None) + if isinstance(value, str): + value = convert_text(value) + output.append(value) return output -def convert_text(column, data: str): - # Convert text answers from front end into digits - # TODO: ensure that categorical columns match the valid answers in FormNew.jsx (L131) +def convert_text(data): + """ + Convert textual data from the frontend into numerical values. + Args: + data (str): The data to convert. + Returns: + int: Converted numerical value. + """ + # TODO: Ensure that categorical columns match valid answers in FormNew.jsx (L131) categorical_cols_integers = [ + {"": 0, "true": 1, "false": 0, "no": 0, "yes": 1}, { - "": 0, - "true": 1, - "false": 0, - "no": 0, - "yes": 1, - "No": 0, - "Yes": 1 - }, - { - 'Grade 0-8': 1, - 'Grade 9': 2, - 'Grade 10': 3, - 'Grade 11': 4, - 'Grade 12 or equivalent': 5, - 'OAC or Grade 13': 6, - 'Some college': 7, - 'Some university': 8, - 'Some apprenticeship': 9, - 'Certificate of Apprenticeship': 10, - 'Journeyperson': 11, - 'Certificate/Diploma': 12, - 'Bachelor’s degree': 13, - 'Post graduate': 14 + 'Grade 0-8': 1, 'Grade 9': 2, 'Grade 12 or equivalent': 5, + 'Bachelor’s degree': 13, 'Post graduate': 14 }, { - 'Renting-private': 1, - 'Renting-subsidized': 2, - 'Boarding or lodging': 3, - 'Homeowner': 4, - 'Living with family/friend': 5, - 'Institution': 6, - 'Temporary second residence': 7, - 'Band-owned home': 8, - 'Homeless or transient': 9, - 'Emergency hostel': 10 + 'Renting-private': 1, 'Homeowner': 4, 'Homeless or transient': 9 }, { - 'No Source of Income': 1, - 'Employment Insurance': 2, - 'Workplace Safety and Insurance Board': 3, - 'Ontario Works applied or receiving': 4, - 'Ontario Disability Support Program applied or receiving': 5, - 'Dependent of someone receiving OW or ODSP': 6, - 'Crown Ward': 7, - 'Employment': 8, - 'Self-Employment': 9, - 'Other (specify)': 10 + 'No Source of Income': 1, 'Employment': 8, 'Self-Employment': 9 } ] for category in categorical_cols_integers: - print(f"data: {data}") - print(f"column: {column}") if data in category: return category[data] - - if isinstance(data, str) and data.isnumeric(): - return int(data) - - return data - -# creates 128 possible combinations in order to run every possibility through model + return int(data) if isinstance(data, str) and data.isnumeric() else data def create_matrix(row): + """ + Create a matrix with all possible combinations of interventions. + Args: + row (list): Baseline data row. + Returns: + numpy.ndarray: Matrix of permutations with baseline data. + """ data = [row.copy() for _ in range(128)] perms = intervention_permutations(7) - data = np.array(data) - perms = np.array(perms) - matrix = np.concatenate((data, perms), axis=1) - return np.array(matrix) -# create matrix of permutations of 1 and 0 of num length + return np.concatenate((data, perms), axis=1) def intervention_permutations(num): - perms = list(product([0, 1], repeat=num)) - return np.array(perms) + """ + Generate all permutations of 0s and 1s for the given length. + Args: + num (int): Length of the permutation. + Returns: + numpy.ndarray: Array of permutations. + """ + return np.array(list(product([0, 1], repeat=num))) def get_baseline_row(row): - print(type(row)) - base_interventions = np.array([0]*7) # no interventions - row = np.array(row) - print(row) - print(type(row)) - line = np.concatenate((row, base_interventions)) - return line + """ + Create a baseline row with no interventions applied. + Args: + row (list): Input data row. + Returns: + numpy.ndarray: Baseline row with no interventions. + """ + base_interventions = np.array([0] * 7) + return np.concatenate((row, base_interventions)) def intervention_row_to_names(row): - names = [] - for i, value in enumerate(row): - if value == 1: - names.append(column_intervention[i]) - return names + """ + Map intervention flags to their corresponding names. + Args: + row (list): Row with intervention flags. + Returns: + list: Names of interventions applied. + """ + return [column_intervention[i] for i, value in enumerate(row) if value == 1] -def process_results(baseline, results): - # Example: +def process_results(baseline, predictions): """ - { - baseline_probability: 80 #baseline percentage point with no interventions - results: [ - (85, [A,B,C]) #new percentange with intervention combinations and list of intervention names - (89, [B,C]) - (91, [D,E]) - ] - } + Process results and map them to a structured output. + Args: + baseline (numpy.ndarray): Baseline prediction. + predictions (numpy.ndarray): Prediction results with interventions. + Returns: + dict: Processed results with baseline and intervention details. """ - result_list = [] - for row in results: - percent = row[-1] - names = intervention_row_to_names(row) - result_list.append((percent, names)) - - output = { - # if it's an array, want the value inside of the array + result_list = [(row[-1], intervention_row_to_names(row[:-1])) for row in predictions] + return { "baseline": baseline[-1], "interventions": result_list, } - return output def interpret_and_calculate(data): + """ + Clean input data, generate predictions, and process results. + Args: + data (dict): Raw input data. + Returns: + dict: Processed prediction results. + """ raw_data = clean_input_data(data) - baseline_row = get_baseline_row(raw_data) - baseline_row = baseline_row.reshape(1, -1) - print("BASELINE ROW IS", baseline_row) + baseline_row = get_baseline_row(raw_data).reshape(1, -1) intervention_rows = create_matrix(raw_data) baseline_prediction = model.predict(baseline_row) - intervention_predictions = model.predict(intervention_rows) - # want shape to be a vertical column, not a row - intervention_predictions = intervention_predictions.reshape(-1, 1) - result_matrix = np.concatenate( - (intervention_rows, intervention_predictions), axis=1) # CHANGED AXIS - - # sort this matrix based on prediction - # print("RESULT SAMPLE::", result_matrix[:5]) - # take all rows and only last column, gives back list of indexes sorted - result_order = result_matrix[:, -1].argsort() - # indexing the matrix by the order - result_matrix = result_matrix[result_order] - - # slice matrix to only top N results - # -8 for interventions and prediction, want top 3, 3 combinations of intervention - result_matrix = result_matrix[-3:, -8:] - # post process results if needed ie make list of names for each row - results = process_results(baseline_prediction, result_matrix) - # build output dict - print(f"RESULTS: {results}") - return results + intervention_predictions = model.predict(intervention_rows).reshape(-1, 1) + result_matrix = np.concatenate((intervention_rows, intervention_predictions), axis=1) + result_matrix = result_matrix[result_matrix[:, -1].argsort()][-3:, -8:] + return process_results(baseline_prediction, result_matrix) if __name__ == "__main__": - print("running") - data = { - "age": "23", - "gender": "1", - "work_experience": "1", - "canada_workex": "1", - "dep_num": "0", - "canada_born": "1", - "citizen_status": "2", - "level_of_schooling": "2", - "fluent_english": "3", - "reading_english_scale": "2", - "speaking_english_scale": "2", - "writing_english_scale": "3", - "numeracy_scale": "2", - "computer_scale": "3", - "transportation_bool": "2", - "caregiver_bool": "1", - "housing": "1", - "income_source": "5", - "felony_bool": "1", - "attending_school": "0", - "currently_employed": "1", - "substance_use": "1", - "time_unemployed": "1", + print("Running predictions...") + sample_data = { + "age": "23", "gender": "1", "work_experience": "1", "canada_workex": "1", "dep_num": "0", + "canada_born": "1", "citizen_status": "2", "level_of_schooling": "2", "fluent_english": "3", + "reading_english_scale": "2", "speaking_english_scale": "2", "writing_english_scale": "3", + "numeracy_scale": "2", "computer_scale": "3", "transportation_bool": "2", "caregiver_bool": "1", + "housing": "1", "income_source": "5", "felony_bool": "1", "attending_school": "0", + "currently_employed": "1", "substance_use": "1", "time_unemployed": "1", "need_mental_health_support_bool": "1" } - # print(data) - results = interpret_and_calculate(data) + results = interpret_and_calculate(sample_data) print(results) diff --git a/app/main.py b/app/main.py index 5b6bf162..5a2483f7 100644 --- a/app/main.py +++ b/app/main.py @@ -15,6 +15,4 @@ allow_origins=["*"], # Allows all origins allow_methods=["*"], # Allows all methods, including OPTIONS allow_headers=["*"], # Allows all headers -) - - +) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1220ef81..5f25b07f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -70,7 +70,7 @@ nbformat==5.8.0 nest-asyncio==1.5.6 notebook==6.5.4 notebook_shim==0.2.2 -numpy==1.24.2 +numpy==24.3.1 packaging==23.2 pandas==2.0.0 pandocfilters==1.5.0 From 586b386412fd9b037c22f29f177b4bb55c2018e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Sat, 23 Nov 2024 19:34:35 -0800 Subject: [PATCH 11/47] Updating schema and database --- .gitignore | 1 + .history/app/clients/schema_20241123184655.py | 48 ++++++++++++++ .history/app/clients/schema_20241123193030.py | 54 +++++++++++++++ .history/app/clients/schema_20241123193200.py | 66 +++++++++++++++++++ .history/app/clients/schema_20241123193203.py | 66 +++++++++++++++++++ .history/app/database_20241123192530.py | 8 +++ .history/app/database_20241123193250.py | 9 +++ .lh/.gitignore.json | 18 +++++ .lh/app/.env.json | 18 +++++ .lh/app/clients/database.py.json | 26 ++++++++ .lh/app/clients/env.py.json | 18 +++++ .lh/app/clients/schema.py.json | 30 +++++++++ .lh/app/database.py.json | 26 ++++++++ app/.env | 2 +- app/clients/schema.py | 24 ++++++- app/database.py | 3 + 16 files changed, 413 insertions(+), 4 deletions(-) create mode 100644 .history/app/clients/schema_20241123184655.py create mode 100644 .history/app/clients/schema_20241123193030.py create mode 100644 .history/app/clients/schema_20241123193200.py create mode 100644 .history/app/clients/schema_20241123193203.py create mode 100644 .history/app/database_20241123192530.py create mode 100644 .history/app/database_20241123193250.py create mode 100644 .lh/.gitignore.json create mode 100644 .lh/app/.env.json create mode 100644 .lh/app/clients/database.py.json create mode 100644 .lh/app/clients/env.py.json create mode 100644 .lh/app/clients/schema.py.json create mode 100644 .lh/app/database.py.json diff --git a/.gitignore b/.gitignore index 281d9273..4c9ce892 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ venv/ .idea __pycache__ .DS_Store +.env \ No newline at end of file diff --git a/.history/app/clients/schema_20241123184655.py b/.history/app/clients/schema_20241123184655.py new file mode 100644 index 00000000..0697df8b --- /dev/null +++ b/.history/app/clients/schema_20241123184655.py @@ -0,0 +1,48 @@ + +from pydantic import BaseModel +from datetime import date +from typing import Optional + +class PredictionInput(BaseModel): + age: int + gender: str + work_experience: int + canada_workex: int + dep_num: int + canada_born: str + citizen_status: str + level_of_schooling: str + fluent_english: str + reading_english_scale: int + speaking_english_scale: int + writing_english_scale: int + numeracy_scale: int + computer_scale: int + transportation_bool: str + caregiver_bool: str + housing: str + income_source: str + felony_bool: str + attending_school: str + currently_employed: str + substance_use: str + time_unemployed: int + need_mental_health_support_bool: str + +class Client(BaseModel): + # ID will be auto-generated in a MangoDB database setup + id: str + first_name: str + last_name: str + email: str + date_of_birth: date + address: Optional[str] = None + phone: Optional[str] = None + +class ClientUpdate(BaseModel): + first_name: Optional[str] = None + last_name: Optional[str] = None + email: Optional[str] = None + date_of_birth: Optional[date] = None + address: Optional[str] = None + phone: Optional[str] = None \ No newline at end of file diff --git a/.history/app/clients/schema_20241123193030.py b/.history/app/clients/schema_20241123193030.py new file mode 100644 index 00000000..679b9d14 --- /dev/null +++ b/.history/app/clients/schema_20241123193030.py @@ -0,0 +1,54 @@ +""" +Schemas for client and prediction data models. +Defines Pydantic models for validation and data manipulation. +""" +from pydantic import BaseModel +from datetime import date +from typing import Optional + + +class PredictionInput(BaseModel): + age: int + gender: str + work_experience: int + canada_workex: int + dep_num: int + canada_born: str + citizen_status: str + level_of_schooling: str + fluent_english: str + reading_english_scale: int + speaking_english_scale: int + writing_english_scale: int + numeracy_scale: int + computer_scale: int + transportation_bool: str + caregiver_bool: str + housing: str + income_source: str + felony_bool: str + attending_school: str + currently_employed: str + substance_use: str + time_unemployed: int + need_mental_health_support_bool: str + + +class Client(BaseModel): + # ID will be auto-generated in a MangoDB database setup + id: str + first_name: str + last_name: str + email: str + date_of_birth: date + address: Optional[str] = None + phone: Optional[str] = None + + +class ClientUpdate(BaseModel): + first_name: Optional[str] = None + last_name: Optional[str] = None + email: Optional[str] = None + date_of_birth: Optional[date] = None + address: Optional[str] = None + phone: Optional[str] = None diff --git a/.history/app/clients/schema_20241123193200.py b/.history/app/clients/schema_20241123193200.py new file mode 100644 index 00000000..4b3bc6dc --- /dev/null +++ b/.history/app/clients/schema_20241123193200.py @@ -0,0 +1,66 @@ +""" +Schemas for client and prediction data models. +Defines Pydantic models for validation and data manipulation. +""" + +from datetime import date +from typing import Optional +from pydantic import BaseModel + + +class PredictionInput(BaseModel): + """ + Schema for prediction input parameters. + Used for validating data submitted for predictions. + """ + age: int + gender: str + work_experience: int + canada_workex: int + dep_num: int + canada_born: str + citizen_status: str + level_of_schooling: str + fluent_english: str + reading_english_scale: int + speaking_english_scale: int + writing_english_scale: int + numeracy_scale: int + computer_scale: int + transportation_bool: str + caregiver_bool: str + housing: str + income_source: str + felony_bool: str + attending_school: str + currently_employed: str + substance_use: str + time_unemployed: int + need_mental_health_support_bool: str + + +class Client(BaseModel): + """ + Schema for client data. + Represents information stored about a client in the database. + """ + id: str + first_name: str + last_name: str + email: str + date_of_birth: date + address: Optional[str] = None + phone: Optional[str] = None + + +class ClientUpdate(BaseModel): + """ + Schema for client update data. + Used for partial updates to client information. + """ + first_name: Optional[str] = None + last_name: Optional[str] = None + email: Optional[str] = None + date_of_birth: Optional[date] = None + address: Optional[str] = None + phone: Optional[str] = None diff --git a/.history/app/clients/schema_20241123193203.py b/.history/app/clients/schema_20241123193203.py new file mode 100644 index 00000000..4b3bc6dc --- /dev/null +++ b/.history/app/clients/schema_20241123193203.py @@ -0,0 +1,66 @@ +""" +Schemas for client and prediction data models. +Defines Pydantic models for validation and data manipulation. +""" + +from datetime import date +from typing import Optional +from pydantic import BaseModel + + +class PredictionInput(BaseModel): + """ + Schema for prediction input parameters. + Used for validating data submitted for predictions. + """ + age: int + gender: str + work_experience: int + canada_workex: int + dep_num: int + canada_born: str + citizen_status: str + level_of_schooling: str + fluent_english: str + reading_english_scale: int + speaking_english_scale: int + writing_english_scale: int + numeracy_scale: int + computer_scale: int + transportation_bool: str + caregiver_bool: str + housing: str + income_source: str + felony_bool: str + attending_school: str + currently_employed: str + substance_use: str + time_unemployed: int + need_mental_health_support_bool: str + + +class Client(BaseModel): + """ + Schema for client data. + Represents information stored about a client in the database. + """ + id: str + first_name: str + last_name: str + email: str + date_of_birth: date + address: Optional[str] = None + phone: Optional[str] = None + + +class ClientUpdate(BaseModel): + """ + Schema for client update data. + Used for partial updates to client information. + """ + first_name: Optional[str] = None + last_name: Optional[str] = None + email: Optional[str] = None + date_of_birth: Optional[date] = None + address: Optional[str] = None + phone: Optional[str] = None diff --git a/.history/app/database_20241123192530.py b/.history/app/database_20241123192530.py new file mode 100644 index 00000000..929268d0 --- /dev/null +++ b/.history/app/database_20241123192530.py @@ -0,0 +1,8 @@ +# Database configuration and connection setup using Motor. + +from motor.motor_asyncio import AsyncIOMotorClient +from app.config import settings + +client = AsyncIOMotorClient(settings.MONGODB_URI) +database = client[settings.MONGODB_NAME] +clients_collection = database.get_collection("clients") diff --git a/.history/app/database_20241123193250.py b/.history/app/database_20241123193250.py new file mode 100644 index 00000000..56c29d7f --- /dev/null +++ b/.history/app/database_20241123193250.py @@ -0,0 +1,9 @@ +""" +Database configuration and connection setup using Motor. +""" +from motor.motor_asyncio import AsyncIOMotorClient +from app.config import settings + +client = AsyncIOMotorClient(settings.MONGODB_URI) +database = client[settings.MONGODB_NAME] +clients_collection = database.get_collection("clients") diff --git a/.lh/.gitignore.json b/.lh/.gitignore.json new file mode 100644 index 00000000..d8dfc3b1 --- /dev/null +++ b/.lh/.gitignore.json @@ -0,0 +1,18 @@ +{ + "sourceFile": ".gitignore", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 0, + "patches": [ + { + "date": 1732416761683, + "content": "Index: \n===================================================================\n--- \n+++ \n" + } + ], + "date": 1732416761683, + "name": "Commit-0", + "content": ".venv\nvenv/\n.idea\n__pycache__\n.DS_Store\n.env" + } + ] +} \ No newline at end of file diff --git a/.lh/app/.env.json b/.lh/app/.env.json new file mode 100644 index 00000000..1318695a --- /dev/null +++ b/.lh/app/.env.json @@ -0,0 +1,18 @@ +{ + "sourceFile": "app/.env", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 0, + "patches": [ + { + "date": 1732416609792, + "content": "Index: \n===================================================================\n--- \n+++ \n" + } + ], + "date": 1732416609792, + "name": "Commit-0", + "content": "MONGODB_URI = mongodb+srv://team:IOi4XgpRxWVkKLv2@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\nMONGODB_NAME = Cluster0\n" + } + ] +} \ No newline at end of file diff --git a/.lh/app/clients/database.py.json b/.lh/app/clients/database.py.json new file mode 100644 index 00000000..0afe2f04 --- /dev/null +++ b/.lh/app/clients/database.py.json @@ -0,0 +1,26 @@ +{ + "sourceFile": "app/clients/database.py", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 2, + "patches": [ + { + "date": 1732076953941, + "content": "Index: \n===================================================================\n--- \n+++ \n" + }, + { + "date": 1732077982474, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -7,5 +7,5 @@\n \n # Initialize MongoDB Client\n client = AsyncIOMotorClient(MONGO_DETAILS)\n database: Database = client[\"client_database\"] # MongoDB Database Name\n-clients_collection: Collection = database[\"clients\"] # MongoDB Collection\n\\ No newline at end of file\n+clients_collection: Collection = database[\"use\"] # MongoDB Collection\n\\ No newline at end of file\n" + }, + { + "date": 1732078102172, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -6,6 +6,6 @@\n MONGO_DETAILS = \"mongodb+srv://labUser:12345@cluster0.7rtlm.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\" # Change this to your MongoDB URL\n \n # Initialize MongoDB Client\n client = AsyncIOMotorClient(MONGO_DETAILS)\n-database: Database = client[\"client_database\"] # MongoDB Database Name\n+database: Database = client[\"Lab_Database\"] # MongoDB Database Name\n clients_collection: Collection = database[\"use\"] # MongoDB Collection\n\\ No newline at end of file\n" + } + ], + "date": 1732076953941, + "name": "Commit-0", + "content": "from motor.motor_asyncio import AsyncIOMotorClient\nfrom pymongo.collection import Collection\nfrom pymongo.database import Database\n\n# MongoDB Connection Configuration\nMONGO_DETAILS = \"mongodb+srv://labUser:12345@cluster0.7rtlm.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\" # Change this to your MongoDB URL\n\n# Initialize MongoDB Client\nclient = AsyncIOMotorClient(MONGO_DETAILS)\ndatabase: Database = client[\"client_database\"] # MongoDB Database Name\nclients_collection: Collection = database[\"clients\"] # MongoDB Collection" + } + ] +} \ No newline at end of file diff --git a/.lh/app/clients/env.py.json b/.lh/app/clients/env.py.json new file mode 100644 index 00000000..8d6e504b --- /dev/null +++ b/.lh/app/clients/env.py.json @@ -0,0 +1,18 @@ +{ + "sourceFile": "app/clients/env.py", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 0, + "patches": [ + { + "date": 1732077271851, + "content": "Index: \n===================================================================\n--- \n+++ \n" + } + ], + "date": 1732077271851, + "name": "Commit-0", + "content": "MONGO_DETAILS = \"mongodb+srv://labUser:12345@cluster0.7rtlm.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\" # Change this to your MongoDB URL" + } + ] +} \ No newline at end of file diff --git a/.lh/app/clients/schema.py.json b/.lh/app/clients/schema.py.json new file mode 100644 index 00000000..2c8b6523 --- /dev/null +++ b/.lh/app/clients/schema.py.json @@ -0,0 +1,30 @@ +{ + "sourceFile": "app/clients/schema.py", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 3, + "patches": [ + { + "date": 1732078343840, + "content": "Index: \n===================================================================\n--- \n+++ \n" + }, + { + "date": 1732078437868, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -30,9 +30,9 @@\n need_mental_health_support_bool: str\n \n class Client(BaseModel):\n # ID will be auto-generated in a MangoDB database setup\n- id: int \n+ id: str \n first_name: str\n last_name: str\n email: str\n date_of_birth: date\n" + }, + { + "date": 1732419030295, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,5 +1,8 @@\n-\n+\"\"\"\n+Schemas for client and prediction data models.\n+Defines Pydantic models for validation and data manipulation.\n+\"\"\"\n from pydantic import BaseModel\n from datetime import date\n from typing import Optional\n \n" + }, + { + "date": 1732419120459, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,14 +1,19 @@\n \"\"\"\n Schemas for client and prediction data models.\n Defines Pydantic models for validation and data manipulation.\n \"\"\"\n-from pydantic import BaseModel\n+\n from datetime import date\n from typing import Optional\n+from pydantic import BaseModel\n \n \n class PredictionInput(BaseModel):\n+ \"\"\"\n+ Schema for prediction input parameters.\n+ Used for validating data submitted for predictions.\n+ \"\"\"\n age: int\n gender: str\n work_experience: int\n canada_workex: int\n@@ -34,9 +39,12 @@\n need_mental_health_support_bool: str\n \n \n class Client(BaseModel):\n- # ID will be auto-generated in a MangoDB database setup\n+ \"\"\"\n+ Schema for client data.\n+ Represents information stored about a client in the database.\n+ \"\"\"\n id: str\n first_name: str\n last_name: str\n email: str\n@@ -45,8 +53,12 @@\n phone: Optional[str] = None\n \n \n class ClientUpdate(BaseModel):\n+ \"\"\"\n+ Schema for client update data.\n+ Used for partial updates to client information.\n+ \"\"\"\n first_name: Optional[str] = None\n last_name: Optional[str] = None\n email: Optional[str] = None\n date_of_birth: Optional[date] = None\n" + } + ], + "date": 1732078343840, + "name": "Commit-0", + "content": "\nfrom pydantic import BaseModel\nfrom datetime import date\nfrom typing import Optional\n\nclass PredictionInput(BaseModel):\n age: int\n gender: str\n work_experience: int\n canada_workex: int\n dep_num: int\n canada_born: str\n citizen_status: str\n level_of_schooling: str\n fluent_english: str\n reading_english_scale: int\n speaking_english_scale: int\n writing_english_scale: int\n numeracy_scale: int\n computer_scale: int\n transportation_bool: str\n caregiver_bool: str\n housing: str\n income_source: str\n felony_bool: str\n attending_school: str\n currently_employed: str\n substance_use: str\n time_unemployed: int\n need_mental_health_support_bool: str\n\nclass Client(BaseModel):\n # ID will be auto-generated in a MangoDB database setup\n id: int \n first_name: str\n last_name: str\n email: str\n date_of_birth: date\n address: Optional[str] = None\n phone: Optional[str] = None\n\nclass ClientUpdate(BaseModel):\n first_name: Optional[str] = None\n last_name: Optional[str] = None\n email: Optional[str] = None\n date_of_birth: Optional[date] = None\n address: Optional[str] = None\n phone: Optional[str] = None" + } + ] +} \ No newline at end of file diff --git a/.lh/app/database.py.json b/.lh/app/database.py.json new file mode 100644 index 00000000..50f2f7da --- /dev/null +++ b/.lh/app/database.py.json @@ -0,0 +1,26 @@ +{ + "sourceFile": "app/database.py", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 2, + "patches": [ + { + "date": 1732418691383, + "content": "Index: \n===================================================================\n--- \n+++ \n" + }, + { + "date": 1732418731388, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,7 +1,6 @@\n-\"\"\"\n-Database configuration and connection setup using Motor.\n-\"\"\"\n+# Database configuration and connection setup using Motor.\n+\n from motor.motor_asyncio import AsyncIOMotorClient\n from app.config import settings\n \n client = AsyncIOMotorClient(settings.MONGODB_URI)\n" + }, + { + "date": 1732419170834, + "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,6 +1,7 @@\n-# Database configuration and connection setup using Motor.\n-\n+\"\"\"\n+Database configuration and connection setup using Motor.\n+\"\"\"\n from motor.motor_asyncio import AsyncIOMotorClient\n from app.config import settings\n \n client = AsyncIOMotorClient(settings.MONGODB_URI)\n" + } + ], + "date": 1732418691383, + "name": "Commit-0", + "content": "\"\"\"\nDatabase configuration and connection setup using Motor.\n\"\"\"\nfrom motor.motor_asyncio import AsyncIOMotorClient\nfrom app.config import settings\n\nclient = AsyncIOMotorClient(settings.MONGODB_URI)\ndatabase = client[settings.MONGODB_NAME]\nclients_collection = database.get_collection(\"clients\")\n" + } + ] +} \ No newline at end of file diff --git a/app/.env b/app/.env index 6fc15234..fe521abb 100644 --- a/app/.env +++ b/app/.env @@ -1,2 +1,2 @@ -MONGODB_URI = mongodb+srv://team:3Y9RHp9ZNRcV0COu@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 +MONGODB_URI = mongodb+srv://team:IOi4XgpRxWVkKLv2@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 MONGODB_NAME = Cluster0 diff --git a/app/clients/schema.py b/app/clients/schema.py index 0697df8b..4b3bc6dc 100644 --- a/app/clients/schema.py +++ b/app/clients/schema.py @@ -1,9 +1,18 @@ +""" +Schemas for client and prediction data models. +Defines Pydantic models for validation and data manipulation. +""" -from pydantic import BaseModel from datetime import date from typing import Optional +from pydantic import BaseModel + class PredictionInput(BaseModel): + """ + Schema for prediction input parameters. + Used for validating data submitted for predictions. + """ age: int gender: str work_experience: int @@ -29,8 +38,12 @@ class PredictionInput(BaseModel): time_unemployed: int need_mental_health_support_bool: str + class Client(BaseModel): - # ID will be auto-generated in a MangoDB database setup + """ + Schema for client data. + Represents information stored about a client in the database. + """ id: str first_name: str last_name: str @@ -39,10 +52,15 @@ class Client(BaseModel): address: Optional[str] = None phone: Optional[str] = None + class ClientUpdate(BaseModel): + """ + Schema for client update data. + Used for partial updates to client information. + """ first_name: Optional[str] = None last_name: Optional[str] = None email: Optional[str] = None date_of_birth: Optional[date] = None address: Optional[str] = None - phone: Optional[str] = None \ No newline at end of file + phone: Optional[str] = None diff --git a/app/database.py b/app/database.py index 0436ad56..56c29d7f 100644 --- a/app/database.py +++ b/app/database.py @@ -1,3 +1,6 @@ +""" +Database configuration and connection setup using Motor. +""" from motor.motor_asyncio import AsyncIOMotorClient from app.config import settings From 345b243b5d1af847395a96e61f960265adb965ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Sat, 23 Nov 2024 19:37:47 -0800 Subject: [PATCH 12/47] Updating schema and database --- .history/app/clients/schema_20241123184655.py | 48 -------------- .history/app/clients/schema_20241123193030.py | 54 --------------- .history/app/clients/schema_20241123193200.py | 66 ------------------- .history/app/clients/schema_20241123193203.py | 66 ------------------- .history/app/database_20241123192530.py | 8 --- .history/app/database_20241123193250.py | 9 --- .lh/.gitignore.json | 18 ----- .lh/.lhignore | 6 -- .lh/app/.env.json | 18 ----- .lh/app/clients/database.py.json | 26 -------- .lh/app/clients/env.py.json | 18 ----- .lh/app/clients/router.py.json | 34 ---------- .lh/app/clients/schema.py.json | 30 --------- .lh/app/clients/service/logic.py.json | 18 ----- .lh/app/database.py.json | 26 -------- 15 files changed, 445 deletions(-) delete mode 100644 .history/app/clients/schema_20241123184655.py delete mode 100644 .history/app/clients/schema_20241123193030.py delete mode 100644 .history/app/clients/schema_20241123193200.py delete mode 100644 .history/app/clients/schema_20241123193203.py delete mode 100644 .history/app/database_20241123192530.py delete mode 100644 .history/app/database_20241123193250.py delete mode 100644 .lh/.gitignore.json delete mode 100644 .lh/.lhignore delete mode 100644 .lh/app/.env.json delete mode 100644 .lh/app/clients/database.py.json delete mode 100644 .lh/app/clients/env.py.json delete mode 100644 .lh/app/clients/router.py.json delete mode 100644 .lh/app/clients/schema.py.json delete mode 100644 .lh/app/clients/service/logic.py.json delete mode 100644 .lh/app/database.py.json diff --git a/.history/app/clients/schema_20241123184655.py b/.history/app/clients/schema_20241123184655.py deleted file mode 100644 index 0697df8b..00000000 --- a/.history/app/clients/schema_20241123184655.py +++ /dev/null @@ -1,48 +0,0 @@ - -from pydantic import BaseModel -from datetime import date -from typing import Optional - -class PredictionInput(BaseModel): - age: int - gender: str - work_experience: int - canada_workex: int - dep_num: int - canada_born: str - citizen_status: str - level_of_schooling: str - fluent_english: str - reading_english_scale: int - speaking_english_scale: int - writing_english_scale: int - numeracy_scale: int - computer_scale: int - transportation_bool: str - caregiver_bool: str - housing: str - income_source: str - felony_bool: str - attending_school: str - currently_employed: str - substance_use: str - time_unemployed: int - need_mental_health_support_bool: str - -class Client(BaseModel): - # ID will be auto-generated in a MangoDB database setup - id: str - first_name: str - last_name: str - email: str - date_of_birth: date - address: Optional[str] = None - phone: Optional[str] = None - -class ClientUpdate(BaseModel): - first_name: Optional[str] = None - last_name: Optional[str] = None - email: Optional[str] = None - date_of_birth: Optional[date] = None - address: Optional[str] = None - phone: Optional[str] = None \ No newline at end of file diff --git a/.history/app/clients/schema_20241123193030.py b/.history/app/clients/schema_20241123193030.py deleted file mode 100644 index 679b9d14..00000000 --- a/.history/app/clients/schema_20241123193030.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Schemas for client and prediction data models. -Defines Pydantic models for validation and data manipulation. -""" -from pydantic import BaseModel -from datetime import date -from typing import Optional - - -class PredictionInput(BaseModel): - age: int - gender: str - work_experience: int - canada_workex: int - dep_num: int - canada_born: str - citizen_status: str - level_of_schooling: str - fluent_english: str - reading_english_scale: int - speaking_english_scale: int - writing_english_scale: int - numeracy_scale: int - computer_scale: int - transportation_bool: str - caregiver_bool: str - housing: str - income_source: str - felony_bool: str - attending_school: str - currently_employed: str - substance_use: str - time_unemployed: int - need_mental_health_support_bool: str - - -class Client(BaseModel): - # ID will be auto-generated in a MangoDB database setup - id: str - first_name: str - last_name: str - email: str - date_of_birth: date - address: Optional[str] = None - phone: Optional[str] = None - - -class ClientUpdate(BaseModel): - first_name: Optional[str] = None - last_name: Optional[str] = None - email: Optional[str] = None - date_of_birth: Optional[date] = None - address: Optional[str] = None - phone: Optional[str] = None diff --git a/.history/app/clients/schema_20241123193200.py b/.history/app/clients/schema_20241123193200.py deleted file mode 100644 index 4b3bc6dc..00000000 --- a/.history/app/clients/schema_20241123193200.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Schemas for client and prediction data models. -Defines Pydantic models for validation and data manipulation. -""" - -from datetime import date -from typing import Optional -from pydantic import BaseModel - - -class PredictionInput(BaseModel): - """ - Schema for prediction input parameters. - Used for validating data submitted for predictions. - """ - age: int - gender: str - work_experience: int - canada_workex: int - dep_num: int - canada_born: str - citizen_status: str - level_of_schooling: str - fluent_english: str - reading_english_scale: int - speaking_english_scale: int - writing_english_scale: int - numeracy_scale: int - computer_scale: int - transportation_bool: str - caregiver_bool: str - housing: str - income_source: str - felony_bool: str - attending_school: str - currently_employed: str - substance_use: str - time_unemployed: int - need_mental_health_support_bool: str - - -class Client(BaseModel): - """ - Schema for client data. - Represents information stored about a client in the database. - """ - id: str - first_name: str - last_name: str - email: str - date_of_birth: date - address: Optional[str] = None - phone: Optional[str] = None - - -class ClientUpdate(BaseModel): - """ - Schema for client update data. - Used for partial updates to client information. - """ - first_name: Optional[str] = None - last_name: Optional[str] = None - email: Optional[str] = None - date_of_birth: Optional[date] = None - address: Optional[str] = None - phone: Optional[str] = None diff --git a/.history/app/clients/schema_20241123193203.py b/.history/app/clients/schema_20241123193203.py deleted file mode 100644 index 4b3bc6dc..00000000 --- a/.history/app/clients/schema_20241123193203.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Schemas for client and prediction data models. -Defines Pydantic models for validation and data manipulation. -""" - -from datetime import date -from typing import Optional -from pydantic import BaseModel - - -class PredictionInput(BaseModel): - """ - Schema for prediction input parameters. - Used for validating data submitted for predictions. - """ - age: int - gender: str - work_experience: int - canada_workex: int - dep_num: int - canada_born: str - citizen_status: str - level_of_schooling: str - fluent_english: str - reading_english_scale: int - speaking_english_scale: int - writing_english_scale: int - numeracy_scale: int - computer_scale: int - transportation_bool: str - caregiver_bool: str - housing: str - income_source: str - felony_bool: str - attending_school: str - currently_employed: str - substance_use: str - time_unemployed: int - need_mental_health_support_bool: str - - -class Client(BaseModel): - """ - Schema for client data. - Represents information stored about a client in the database. - """ - id: str - first_name: str - last_name: str - email: str - date_of_birth: date - address: Optional[str] = None - phone: Optional[str] = None - - -class ClientUpdate(BaseModel): - """ - Schema for client update data. - Used for partial updates to client information. - """ - first_name: Optional[str] = None - last_name: Optional[str] = None - email: Optional[str] = None - date_of_birth: Optional[date] = None - address: Optional[str] = None - phone: Optional[str] = None diff --git a/.history/app/database_20241123192530.py b/.history/app/database_20241123192530.py deleted file mode 100644 index 929268d0..00000000 --- a/.history/app/database_20241123192530.py +++ /dev/null @@ -1,8 +0,0 @@ -# Database configuration and connection setup using Motor. - -from motor.motor_asyncio import AsyncIOMotorClient -from app.config import settings - -client = AsyncIOMotorClient(settings.MONGODB_URI) -database = client[settings.MONGODB_NAME] -clients_collection = database.get_collection("clients") diff --git a/.history/app/database_20241123193250.py b/.history/app/database_20241123193250.py deleted file mode 100644 index 56c29d7f..00000000 --- a/.history/app/database_20241123193250.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Database configuration and connection setup using Motor. -""" -from motor.motor_asyncio import AsyncIOMotorClient -from app.config import settings - -client = AsyncIOMotorClient(settings.MONGODB_URI) -database = client[settings.MONGODB_NAME] -clients_collection = database.get_collection("clients") diff --git a/.lh/.gitignore.json b/.lh/.gitignore.json deleted file mode 100644 index d8dfc3b1..00000000 --- a/.lh/.gitignore.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "sourceFile": ".gitignore", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 0, - "patches": [ - { - "date": 1732416761683, - "content": "Index: \n===================================================================\n--- \n+++ \n" - } - ], - "date": 1732416761683, - "name": "Commit-0", - "content": ".venv\nvenv/\n.idea\n__pycache__\n.DS_Store\n.env" - } - ] -} \ No newline at end of file diff --git a/.lh/.lhignore b/.lh/.lhignore deleted file mode 100644 index 1de51008..00000000 --- a/.lh/.lhignore +++ /dev/null @@ -1,6 +0,0 @@ -# list file to not track by the local-history extension. comment line starts with a '#' character -# each line describe a regular expression pattern (search for 'Javascript regex') -# it will relate to the workspace directory root. for example: -# '.*\.txt' ignores any file with 'txt' extension -# '/test/.*' ignores all the files under the 'test' directory -# '.*/test/.*' ignores all the files under any 'test' directory (even under sub-folders) diff --git a/.lh/app/.env.json b/.lh/app/.env.json deleted file mode 100644 index 1318695a..00000000 --- a/.lh/app/.env.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "sourceFile": "app/.env", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 0, - "patches": [ - { - "date": 1732416609792, - "content": "Index: \n===================================================================\n--- \n+++ \n" - } - ], - "date": 1732416609792, - "name": "Commit-0", - "content": "MONGODB_URI = mongodb+srv://team:IOi4XgpRxWVkKLv2@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\nMONGODB_NAME = Cluster0\n" - } - ] -} \ No newline at end of file diff --git a/.lh/app/clients/database.py.json b/.lh/app/clients/database.py.json deleted file mode 100644 index 0afe2f04..00000000 --- a/.lh/app/clients/database.py.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "sourceFile": "app/clients/database.py", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 2, - "patches": [ - { - "date": 1732076953941, - "content": "Index: \n===================================================================\n--- \n+++ \n" - }, - { - "date": 1732077982474, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -7,5 +7,5 @@\n \n # Initialize MongoDB Client\n client = AsyncIOMotorClient(MONGO_DETAILS)\n database: Database = client[\"client_database\"] # MongoDB Database Name\n-clients_collection: Collection = database[\"clients\"] # MongoDB Collection\n\\ No newline at end of file\n+clients_collection: Collection = database[\"use\"] # MongoDB Collection\n\\ No newline at end of file\n" - }, - { - "date": 1732078102172, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -6,6 +6,6 @@\n MONGO_DETAILS = \"mongodb+srv://labUser:12345@cluster0.7rtlm.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\" # Change this to your MongoDB URL\n \n # Initialize MongoDB Client\n client = AsyncIOMotorClient(MONGO_DETAILS)\n-database: Database = client[\"client_database\"] # MongoDB Database Name\n+database: Database = client[\"Lab_Database\"] # MongoDB Database Name\n clients_collection: Collection = database[\"use\"] # MongoDB Collection\n\\ No newline at end of file\n" - } - ], - "date": 1732076953941, - "name": "Commit-0", - "content": "from motor.motor_asyncio import AsyncIOMotorClient\nfrom pymongo.collection import Collection\nfrom pymongo.database import Database\n\n# MongoDB Connection Configuration\nMONGO_DETAILS = \"mongodb+srv://labUser:12345@cluster0.7rtlm.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\" # Change this to your MongoDB URL\n\n# Initialize MongoDB Client\nclient = AsyncIOMotorClient(MONGO_DETAILS)\ndatabase: Database = client[\"client_database\"] # MongoDB Database Name\nclients_collection: Collection = database[\"clients\"] # MongoDB Collection" - } - ] -} \ No newline at end of file diff --git a/.lh/app/clients/env.py.json b/.lh/app/clients/env.py.json deleted file mode 100644 index 8d6e504b..00000000 --- a/.lh/app/clients/env.py.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "sourceFile": "app/clients/env.py", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 0, - "patches": [ - { - "date": 1732077271851, - "content": "Index: \n===================================================================\n--- \n+++ \n" - } - ], - "date": 1732077271851, - "name": "Commit-0", - "content": "MONGO_DETAILS = \"mongodb+srv://labUser:12345@cluster0.7rtlm.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0\" # Change this to your MongoDB URL" - } - ] -} \ No newline at end of file diff --git a/.lh/app/clients/router.py.json b/.lh/app/clients/router.py.json deleted file mode 100644 index 40ac094f..00000000 --- a/.lh/app/clients/router.py.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "sourceFile": "app/clients/router.py", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 4, - "patches": [ - { - "date": 1730875596606, - "content": "Index: \n===================================================================\n--- \n+++ \n" - }, - { - "date": 1730875608612, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -57,6 +57,6 @@\n return\n \n \n @router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n-async def get_all_clients():\n+async def delete_all_clients():\n return list(mock_clients_db.values())\n" - }, - { - "date": 1730875707491, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -50,12 +50,12 @@\n \n \n @router.delete(\"/clients/{id}\", response_model=None, summary=\"Delete client by ID\")\n async def delete_client_by_id(id: int):\n- client = mock_clients_db.get(id)\n+ client = mock_clients_db.pop(id, None)\n if client is None:\n raise HTTPException(status_code=404, detail=\"Client not found\")\n- return\n+ return {\"message\": f\"Client with ID {id} deleted successfully.\"}\n \n \n @router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n async def delete_all_clients():\n" - }, - { - "date": 1730875735473, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -58,5 +58,6 @@\n \n \n @router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n async def delete_all_clients():\n- return list(mock_clients_db.values())\n+ mock_clients_db.clear()\n+ return {\"message\": \"All clients deleted successfully.\"}\n" - }, - { - "date": 1730875916113, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -56,8 +56,8 @@\n raise HTTPException(status_code=404, detail=\"Client not found\")\n return {\"message\": f\"Client with ID {id} deleted successfully.\"}\n \n \n-@router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\n+@router.delete(\"/clients\", response_model=None, summary=\"Delete all clients\")\n async def delete_all_clients():\n mock_clients_db.clear()\n return {\"message\": \"All clients deleted successfully.\"}\n" - } - ], - "date": 1730875596606, - "name": "Commit-0", - "content": "from fastapi import APIRouter, HTTPException\nfrom fastapi.responses import HTMLResponse\nfrom typing import List\n\nfrom app.clients.service.logic import interpret_and_calculate\nfrom app.clients.schema import PredictionInput, Client\n\nrouter = APIRouter(prefix=\"/clients\", tags=[\"clients\"])\n\nmock_clients_db = {\n 1: Client(\n id=1,\n first_name=\"Amy\",\n last_name=\"Doe\",\n email=\"amy.doe@example.com\",\n date_of_birth=\"1995-04-23\",\n address=\"123 Main St, Springfield\",\n phone=\"123-456-7890\"\n ),\n 2: Client(\n id=2,\n first_name=\"Bob\",\n last_name=\"Smith\",\n email=\"bob.smith@example.com\",\n date_of_birth=\"1999-08-17\",\n address=\"456 Elm St, Springfield\",\n phone=\"098-765-4321\"\n ),\n}\n\n\n@router.post(\"/predictions\")\nasync def predict(data: PredictionInput):\n print(\"HERE\")\n print(data.model_dump())\n return interpret_and_calculate(data.model_dump())\n\n\n@router.get(\"/clients/{id}\", response_model=Client, summary=\"Retrieve client by ID\")\nasync def get_client_by_id(id: int):\n client = mock_clients_db.get(id)\n if client is None:\n raise HTTPException(status_code=404, detail=\"Client not found\")\n return client\n\n\n@router.get(\"/clients\", response_model=List[Client], summary=\"Retrieve all clients\")\nasync def get_all_clients():\n return list(mock_clients_db.values())\n\n\n@router.delete(\"/clients/{id}\", response_model=None, summary=\"Delete client by ID\")\nasync def delete_client_by_id(id: int):\n client = mock_clients_db.get(id)\n if client is None:\n raise HTTPException(status_code=404, detail=\"Client not found\")\n return\n\n\n@router.get(\"/clients\", response_model=None, summary=\"Delete all clients\")\nasync def get_all_clients():\n return list(mock_clients_db.values())\n" - } - ] -} \ No newline at end of file diff --git a/.lh/app/clients/schema.py.json b/.lh/app/clients/schema.py.json deleted file mode 100644 index 2c8b6523..00000000 --- a/.lh/app/clients/schema.py.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "sourceFile": "app/clients/schema.py", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 3, - "patches": [ - { - "date": 1732078343840, - "content": "Index: \n===================================================================\n--- \n+++ \n" - }, - { - "date": 1732078437868, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -30,9 +30,9 @@\n need_mental_health_support_bool: str\n \n class Client(BaseModel):\n # ID will be auto-generated in a MangoDB database setup\n- id: int \n+ id: str \n first_name: str\n last_name: str\n email: str\n date_of_birth: date\n" - }, - { - "date": 1732419030295, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,5 +1,8 @@\n-\n+\"\"\"\n+Schemas for client and prediction data models.\n+Defines Pydantic models for validation and data manipulation.\n+\"\"\"\n from pydantic import BaseModel\n from datetime import date\n from typing import Optional\n \n" - }, - { - "date": 1732419120459, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,14 +1,19 @@\n \"\"\"\n Schemas for client and prediction data models.\n Defines Pydantic models for validation and data manipulation.\n \"\"\"\n-from pydantic import BaseModel\n+\n from datetime import date\n from typing import Optional\n+from pydantic import BaseModel\n \n \n class PredictionInput(BaseModel):\n+ \"\"\"\n+ Schema for prediction input parameters.\n+ Used for validating data submitted for predictions.\n+ \"\"\"\n age: int\n gender: str\n work_experience: int\n canada_workex: int\n@@ -34,9 +39,12 @@\n need_mental_health_support_bool: str\n \n \n class Client(BaseModel):\n- # ID will be auto-generated in a MangoDB database setup\n+ \"\"\"\n+ Schema for client data.\n+ Represents information stored about a client in the database.\n+ \"\"\"\n id: str\n first_name: str\n last_name: str\n email: str\n@@ -45,8 +53,12 @@\n phone: Optional[str] = None\n \n \n class ClientUpdate(BaseModel):\n+ \"\"\"\n+ Schema for client update data.\n+ Used for partial updates to client information.\n+ \"\"\"\n first_name: Optional[str] = None\n last_name: Optional[str] = None\n email: Optional[str] = None\n date_of_birth: Optional[date] = None\n" - } - ], - "date": 1732078343840, - "name": "Commit-0", - "content": "\nfrom pydantic import BaseModel\nfrom datetime import date\nfrom typing import Optional\n\nclass PredictionInput(BaseModel):\n age: int\n gender: str\n work_experience: int\n canada_workex: int\n dep_num: int\n canada_born: str\n citizen_status: str\n level_of_schooling: str\n fluent_english: str\n reading_english_scale: int\n speaking_english_scale: int\n writing_english_scale: int\n numeracy_scale: int\n computer_scale: int\n transportation_bool: str\n caregiver_bool: str\n housing: str\n income_source: str\n felony_bool: str\n attending_school: str\n currently_employed: str\n substance_use: str\n time_unemployed: int\n need_mental_health_support_bool: str\n\nclass Client(BaseModel):\n # ID will be auto-generated in a MangoDB database setup\n id: int \n first_name: str\n last_name: str\n email: str\n date_of_birth: date\n address: Optional[str] = None\n phone: Optional[str] = None\n\nclass ClientUpdate(BaseModel):\n first_name: Optional[str] = None\n last_name: Optional[str] = None\n email: Optional[str] = None\n date_of_birth: Optional[date] = None\n address: Optional[str] = None\n phone: Optional[str] = None" - } - ] -} \ No newline at end of file diff --git a/.lh/app/clients/service/logic.py.json b/.lh/app/clients/service/logic.py.json deleted file mode 100644 index aacd9c85..00000000 --- a/.lh/app/clients/service/logic.py.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "sourceFile": "app/clients/service/logic.py", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 0, - "patches": [ - { - "date": 1730875940736, - "content": "Index: \n===================================================================\n--- \n+++ \n" - } - ], - "date": 1730875940736, - "name": "Commit-0", - "content": "import os\nfrom typing import List\nimport pandas as pd\nimport json\nimport numpy as np\nimport pickle\nfrom itertools import combinations_with_replacement\nfrom itertools import product\n\ncolumn_intervention = [\n 'Life Stabilization',\n 'General Employment Assistance Services',\n 'Retention Services',\n 'Specialized Services',\n 'Employment-Related Financial Supports for Job Seekers and Employers',\n 'Employer Financial Supports',\n 'Enhanced Referrals for Skills Development'\n]\n\n# loads the model into logic\n\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nfilename = os.path.join(current_dir, 'model.pkl')\nmodel = pickle.load(open(filename, \"rb\"))\n\n\ndef clean_input_data(data):\n # translate input into wahtever we trained the model on, numerical data in a specific order\n columns = [\"age\", \"gender\", \"work_experience\", \"canada_workex\", \"dep_num\",\t\"canada_born\",\n \"citizen_status\",\t\"level_of_schooling\",\t\"fluent_english\",\t\"reading_english_scale\",\n \"speaking_english_scale\",\t\"writing_english_scale\",\t\"numeracy_scale\",\t\"computer_scale\",\n \"transportation_bool\",\t\"caregiver_bool\",\t\"housing\",\t\"income_source\",\t\"felony_bool\",\t\"attending_school\",\n \"currently_employed\",\t\"substance_use\",\t\"time_unemployed\",\t\"need_mental_health_support_bool\"]\n demographics = {\n 'age': data['age'],\n 'gender': data['gender'],\n 'work_experience': data['work_experience'],\n 'canada_workex': data['canada_workex'],\n 'dep_num': data['dep_num'],\n 'canada_born': data['canada_born'],\n 'citizen_status': data['citizen_status'],\n 'level_of_schooling': data['level_of_schooling'],\n 'fluent_english': data['fluent_english'],\n 'reading_english_scale': data['reading_english_scale'],\n 'speaking_english_scale': data['speaking_english_scale'],\n 'writing_english_scale': data['writing_english_scale'],\n 'numeracy_scale': data['numeracy_scale'],\n 'computer_scale': data['computer_scale'],\n 'transportation_bool': data['transportation_bool'],\n 'caregiver_bool': data['caregiver_bool'],\n 'housing': data['housing'],\n 'income_source': data['income_source'],\n 'felony_bool': data['felony_bool'],\n 'attending_school': data['attending_school'],\n 'currently_employed': data['currently_employed'],\n 'substance_use': data['substance_use'],\n 'time_unemployed': data['time_unemployed'],\n 'need_mental_health_support_bool': data['need_mental_health_support_bool']\n }\n output = []\n for column in columns:\n # default is None, and if you want to pass a value, can return any value\n data = demographics.get(column, None)\n if isinstance(data, str):\n data = convert_text(column, data)\n output.append(data)\n return output\n\n\ndef convert_text(column, data: str):\n # Convert text answers from front end into digits\n # TODO: ensure that categorical columns match the valid answers in FormNew.jsx (L131)\n categorical_cols_integers = [\n {\n \"\": 0,\n \"true\": 1,\n \"false\": 0,\n \"no\": 0,\n \"yes\": 1,\n \"No\": 0,\n \"Yes\": 1\n },\n {\n 'Grade 0-8': 1,\n 'Grade 9': 2,\n 'Grade 10': 3,\n 'Grade 11': 4,\n 'Grade 12 or equivalent': 5,\n 'OAC or Grade 13': 6,\n 'Some college': 7,\n 'Some university': 8,\n 'Some apprenticeship': 9,\n 'Certificate of Apprenticeship': 10,\n 'Journeyperson': 11,\n 'Certificate/Diploma': 12,\n 'Bachelor’s degree': 13,\n 'Post graduate': 14\n },\n {\n 'Renting-private': 1,\n 'Renting-subsidized': 2,\n 'Boarding or lodging': 3,\n 'Homeowner': 4,\n 'Living with family/friend': 5,\n 'Institution': 6,\n 'Temporary second residence': 7,\n 'Band-owned home': 8,\n 'Homeless or transient': 9,\n 'Emergency hostel': 10\n },\n {\n 'No Source of Income': 1,\n 'Employment Insurance': 2,\n 'Workplace Safety and Insurance Board': 3,\n 'Ontario Works applied or receiving': 4,\n 'Ontario Disability Support Program applied or receiving': 5,\n 'Dependent of someone receiving OW or ODSP': 6,\n 'Crown Ward': 7,\n 'Employment': 8,\n 'Self-Employment': 9,\n 'Other (specify)': 10\n }\n ]\n for category in categorical_cols_integers:\n print(f\"data: {data}\")\n print(f\"column: {column}\")\n if data in category:\n return category[data]\n\n if isinstance(data, str) and data.isnumeric():\n return int(data)\n\n return data\n\n# creates 128 possible combinations in order to run every possibility through model\n\n\ndef create_matrix(row):\n data = [row.copy() for _ in range(128)]\n perms = intervention_permutations(7)\n data = np.array(data)\n perms = np.array(perms)\n matrix = np.concatenate((data, perms), axis=1)\n return np.array(matrix)\n# create matrix of permutations of 1 and 0 of num length\n\n\ndef intervention_permutations(num):\n perms = list(product([0, 1], repeat=num))\n return np.array(perms)\n\n\ndef get_baseline_row(row):\n print(type(row))\n base_interventions = np.array([0]*7) # no interventions\n row = np.array(row)\n print(row)\n print(type(row))\n line = np.concatenate((row, base_interventions))\n return line\n\n\ndef intervention_row_to_names(row):\n names = []\n for i, value in enumerate(row):\n if value == 1:\n names.append(column_intervention[i])\n return names\n\n\ndef process_results(baseline, results):\n # Example:\n \"\"\"\n {\n baseline_probability: 80 #baseline percentage point with no interventions\n results: [\n (85, [A,B,C]) #new percentange with intervention combinations and list of intervention names\n (89, [B,C])\n (91, [D,E])\n ]\n }\n \"\"\"\n result_list = []\n for row in results:\n percent = row[-1]\n names = intervention_row_to_names(row)\n result_list.append((percent, names))\n\n output = {\n # if it's an array, want the value inside of the array\n \"baseline\": baseline[-1],\n \"interventions\": result_list,\n }\n return output\n\n\ndef interpret_and_calculate(data):\n raw_data = clean_input_data(data)\n baseline_row = get_baseline_row(raw_data)\n baseline_row = baseline_row.reshape(1, -1)\n print(\"BASELINE ROW IS\", baseline_row)\n intervention_rows = create_matrix(raw_data)\n baseline_prediction = model.predict(baseline_row)\n intervention_predictions = model.predict(intervention_rows)\n # want shape to be a vertical column, not a row\n intervention_predictions = intervention_predictions.reshape(-1, 1)\n result_matrix = np.concatenate(\n (intervention_rows, intervention_predictions), axis=1) # CHANGED AXIS\n\n # sort this matrix based on prediction\n # print(\"RESULT SAMPLE::\", result_matrix[:5])\n # take all rows and only last column, gives back list of indexes sorted\n result_order = result_matrix[:, -1].argsort()\n # indexing the matrix by the order\n result_matrix = result_matrix[result_order]\n\n # slice matrix to only top N results\n # -8 for interventions and prediction, want top 3, 3 combinations of intervention\n result_matrix = result_matrix[-3:, -8:]\n # post process results if needed ie make list of names for each row\n results = process_results(baseline_prediction, result_matrix)\n # build output dict\n print(f\"RESULTS: {results}\")\n return results\n\n\nif __name__ == \"__main__\":\n print(\"running\")\n data = {\n \"age\": \"23\",\n \"gender\": \"1\",\n \"work_experience\": \"1\",\n \"canada_workex\": \"1\",\n \"dep_num\": \"0\",\n \"canada_born\": \"1\",\n \"citizen_status\": \"2\",\n \"level_of_schooling\": \"2\",\n \"fluent_english\": \"3\",\n \"reading_english_scale\": \"2\",\n \"speaking_english_scale\": \"2\",\n \"writing_english_scale\": \"3\",\n \"numeracy_scale\": \"2\",\n \"computer_scale\": \"3\",\n \"transportation_bool\": \"2\",\n \"caregiver_bool\": \"1\",\n \"housing\": \"1\",\n \"income_source\": \"5\",\n \"felony_bool\": \"1\",\n \"attending_school\": \"0\",\n \"currently_employed\": \"1\",\n \"substance_use\": \"1\",\n \"time_unemployed\": \"1\",\n \"need_mental_health_support_bool\": \"1\"\n }\n # print(data)\n results = interpret_and_calculate(data)\n print(results)\n" - } - ] -} \ No newline at end of file diff --git a/.lh/app/database.py.json b/.lh/app/database.py.json deleted file mode 100644 index 50f2f7da..00000000 --- a/.lh/app/database.py.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "sourceFile": "app/database.py", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 2, - "patches": [ - { - "date": 1732418691383, - "content": "Index: \n===================================================================\n--- \n+++ \n" - }, - { - "date": 1732418731388, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,7 +1,6 @@\n-\"\"\"\n-Database configuration and connection setup using Motor.\n-\"\"\"\n+# Database configuration and connection setup using Motor.\n+\n from motor.motor_asyncio import AsyncIOMotorClient\n from app.config import settings\n \n client = AsyncIOMotorClient(settings.MONGODB_URI)\n" - }, - { - "date": 1732419170834, - "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,6 +1,7 @@\n-# Database configuration and connection setup using Motor.\n-\n+\"\"\"\n+Database configuration and connection setup using Motor.\n+\"\"\"\n from motor.motor_asyncio import AsyncIOMotorClient\n from app.config import settings\n \n client = AsyncIOMotorClient(settings.MONGODB_URI)\n" - } - ], - "date": 1732418691383, - "name": "Commit-0", - "content": "\"\"\"\nDatabase configuration and connection setup using Motor.\n\"\"\"\nfrom motor.motor_asyncio import AsyncIOMotorClient\nfrom app.config import settings\n\nclient = AsyncIOMotorClient(settings.MONGODB_URI)\ndatabase = client[settings.MONGODB_NAME]\nclients_collection = database.get_collection(\"clients\")\n" - } - ] -} \ No newline at end of file From 3b1a99c88098d525e9e7106251e2a6703dba3dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Sat, 23 Nov 2024 20:06:10 -0800 Subject: [PATCH 13/47] Updating python version on pylint --- .github/workflows/pylint.yml | 2 +- .../workflows/pylint_20241123184655.yml | 24 +++++++++++++++++++ .../workflows/pylint_20241123200343.yml | 24 +++++++++++++++++++ .history/runServer_20241123194450.sh | 7 ++++++ .history/runServer_20241123200203.sh | 7 ++++++ .lh/.github/workflows/pylint.yml.json | 18 ++++++++++++++ .lh/runServer.sh.json | 18 ++++++++++++++ 7 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .history/.github/workflows/pylint_20241123184655.yml create mode 100644 .history/.github/workflows/pylint_20241123200343.yml create mode 100644 .history/runServer_20241123194450.sh create mode 100644 .history/runServer_20241123200203.sh create mode 100644 .lh/.github/workflows/pylint.yml.json create mode 100644 .lh/runServer.sh.json diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 637651cf..3ea6feb5 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} diff --git a/.history/.github/workflows/pylint_20241123184655.yml b/.history/.github/workflows/pylint_20241123184655.yml new file mode 100644 index 00000000..637651cf --- /dev/null +++ b/.history/.github/workflows/pylint_20241123184655.yml @@ -0,0 +1,24 @@ +name: Pylint + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint ./app; # pylint $(git ls-files '*.py') + diff --git a/.history/.github/workflows/pylint_20241123200343.yml b/.history/.github/workflows/pylint_20241123200343.yml new file mode 100644 index 00000000..3ea6feb5 --- /dev/null +++ b/.history/.github/workflows/pylint_20241123200343.yml @@ -0,0 +1,24 @@ +name: Pylint + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint ./app; # pylint $(git ls-files '*.py') + diff --git a/.history/runServer_20241123194450.sh b/.history/runServer_20241123194450.sh new file mode 100644 index 00000000..ce403bc6 --- /dev/null +++ b/.history/runServer_20241123194450.sh @@ -0,0 +1,7 @@ +python3 -m venv venv +source venv/bin/activate +export PYTHONPATH="$(pwd):$PYTHONPATH" +pip install uvicorn +pip install -r requirements.txt +cd app +uvicorn main:app --reload \ No newline at end of file diff --git a/.history/runServer_20241123200203.sh b/.history/runServer_20241123200203.sh new file mode 100644 index 00000000..ce403bc6 --- /dev/null +++ b/.history/runServer_20241123200203.sh @@ -0,0 +1,7 @@ +python3 -m venv venv +source venv/bin/activate +export PYTHONPATH="$(pwd):$PYTHONPATH" +pip install uvicorn +pip install -r requirements.txt +cd app +uvicorn main:app --reload \ No newline at end of file diff --git a/.lh/.github/workflows/pylint.yml.json b/.lh/.github/workflows/pylint.yml.json new file mode 100644 index 00000000..c30a4fd0 --- /dev/null +++ b/.lh/.github/workflows/pylint.yml.json @@ -0,0 +1,18 @@ +{ + "sourceFile": ".github/workflows/pylint.yml", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 0, + "patches": [ + { + "date": 1732421023123, + "content": "Index: \n===================================================================\n--- \n+++ \n" + } + ], + "date": 1732421023123, + "name": "Commit-0", + "content": "name: Pylint\n\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n python-version: [\"3.8\", \"3.9\", \"3.10\", \"3.11\"]\n steps:\n - uses: actions/checkout@v4\n - name: Set up Python ${{ matrix.python-version }}\n uses: actions/setup-python@v3\n with:\n python-version: ${{ matrix.python-version }}\n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install pylint\n - name: Analysing the code with pylint\n run: |\n pylint ./app; # pylint $(git ls-files '*.py')\n \n" + } + ] +} \ No newline at end of file diff --git a/.lh/runServer.sh.json b/.lh/runServer.sh.json new file mode 100644 index 00000000..e8a4cc5c --- /dev/null +++ b/.lh/runServer.sh.json @@ -0,0 +1,18 @@ +{ + "sourceFile": "runServer.sh", + "activeCommit": 0, + "commits": [ + { + "activePatchIndex": 0, + "patches": [ + { + "date": 1732420923025, + "content": "Index: \n===================================================================\n--- \n+++ \n" + } + ], + "date": 1732420923025, + "name": "Commit-0", + "content": "python3 -m venv venv\nsource venv/bin/activate\nexport PYTHONPATH=\"$(pwd):$PYTHONPATH\"\npip install uvicorn\npip install -r requirements.txt\ncd app\nuvicorn main:app --reload" + } + ] +} \ No newline at end of file From d66c57655de8692a869bb0e2a08bfcb758a38424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Sat, 23 Nov 2024 20:06:28 -0800 Subject: [PATCH 14/47] Updating python version on pylint --- .../workflows/pylint_20241123184655.yml | 24 ------------------- .../workflows/pylint_20241123200343.yml | 24 ------------------- .history/runServer_20241123194450.sh | 7 ------ .history/runServer_20241123200203.sh | 7 ------ .lh/.github/workflows/pylint.yml.json | 18 -------------- .lh/runServer.sh.json | 18 -------------- 6 files changed, 98 deletions(-) delete mode 100644 .history/.github/workflows/pylint_20241123184655.yml delete mode 100644 .history/.github/workflows/pylint_20241123200343.yml delete mode 100644 .history/runServer_20241123194450.sh delete mode 100644 .history/runServer_20241123200203.sh delete mode 100644 .lh/.github/workflows/pylint.yml.json delete mode 100644 .lh/runServer.sh.json diff --git a/.history/.github/workflows/pylint_20241123184655.yml b/.history/.github/workflows/pylint_20241123184655.yml deleted file mode 100644 index 637651cf..00000000 --- a/.history/.github/workflows/pylint_20241123184655.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Pylint - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10"] - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint - - name: Analysing the code with pylint - run: | - pylint ./app; # pylint $(git ls-files '*.py') - diff --git a/.history/.github/workflows/pylint_20241123200343.yml b/.history/.github/workflows/pylint_20241123200343.yml deleted file mode 100644 index 3ea6feb5..00000000 --- a/.history/.github/workflows/pylint_20241123200343.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Pylint - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint - - name: Analysing the code with pylint - run: | - pylint ./app; # pylint $(git ls-files '*.py') - diff --git a/.history/runServer_20241123194450.sh b/.history/runServer_20241123194450.sh deleted file mode 100644 index ce403bc6..00000000 --- a/.history/runServer_20241123194450.sh +++ /dev/null @@ -1,7 +0,0 @@ -python3 -m venv venv -source venv/bin/activate -export PYTHONPATH="$(pwd):$PYTHONPATH" -pip install uvicorn -pip install -r requirements.txt -cd app -uvicorn main:app --reload \ No newline at end of file diff --git a/.history/runServer_20241123200203.sh b/.history/runServer_20241123200203.sh deleted file mode 100644 index ce403bc6..00000000 --- a/.history/runServer_20241123200203.sh +++ /dev/null @@ -1,7 +0,0 @@ -python3 -m venv venv -source venv/bin/activate -export PYTHONPATH="$(pwd):$PYTHONPATH" -pip install uvicorn -pip install -r requirements.txt -cd app -uvicorn main:app --reload \ No newline at end of file diff --git a/.lh/.github/workflows/pylint.yml.json b/.lh/.github/workflows/pylint.yml.json deleted file mode 100644 index c30a4fd0..00000000 --- a/.lh/.github/workflows/pylint.yml.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "sourceFile": ".github/workflows/pylint.yml", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 0, - "patches": [ - { - "date": 1732421023123, - "content": "Index: \n===================================================================\n--- \n+++ \n" - } - ], - "date": 1732421023123, - "name": "Commit-0", - "content": "name: Pylint\n\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n python-version: [\"3.8\", \"3.9\", \"3.10\", \"3.11\"]\n steps:\n - uses: actions/checkout@v4\n - name: Set up Python ${{ matrix.python-version }}\n uses: actions/setup-python@v3\n with:\n python-version: ${{ matrix.python-version }}\n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install pylint\n - name: Analysing the code with pylint\n run: |\n pylint ./app; # pylint $(git ls-files '*.py')\n \n" - } - ] -} \ No newline at end of file diff --git a/.lh/runServer.sh.json b/.lh/runServer.sh.json deleted file mode 100644 index e8a4cc5c..00000000 --- a/.lh/runServer.sh.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "sourceFile": "runServer.sh", - "activeCommit": 0, - "commits": [ - { - "activePatchIndex": 0, - "patches": [ - { - "date": 1732420923025, - "content": "Index: \n===================================================================\n--- \n+++ \n" - } - ], - "date": 1732420923025, - "name": "Commit-0", - "content": "python3 -m venv venv\nsource venv/bin/activate\nexport PYTHONPATH=\"$(pwd):$PYTHONPATH\"\npip install uvicorn\npip install -r requirements.txt\ncd app\nuvicorn main:app --reload" - } - ] -} \ No newline at end of file From 54ab6dc76a35ab6eca6379c0afa412dd3e3cc101 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Sat, 23 Nov 2024 20:26:33 -0800 Subject: [PATCH 15/47] fix pylint errors --- app/config.py | 22 +++++++++++++++++++++- app/main.py | 9 +++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/config.py b/app/config.py index 65fa7103..1bbfc3df 100644 --- a/app/config.py +++ b/app/config.py @@ -1,10 +1,30 @@ +""" +This module contains the configuration settings for the application. +It uses Pydantic's BaseSettings to load settings from environment variables. +""" + from pydantic_settings import BaseSettings +from pydantic import BaseModel class Settings(BaseSettings): + """ + Settings class to manage application configuration. + + Attributes: + MONGODB_URI (str): MongoDB connection URI. + MONGODB_NAME (str): MongoDB database name. + """ MONGODB_URI: str MONGODB_NAME: str - class Config: + class Config(BaseModel): + """ + Config class to specify Pydantic settings behavior. + + Attributes: + env_file (str): Path to the .env file. + """ env_file = ".env" +# Instantiate the settings object to be used throughout the application settings = Settings() diff --git a/app/main.py b/app/main.py index 5a2483f7..3d6ce42c 100644 --- a/app/main.py +++ b/app/main.py @@ -1,9 +1,14 @@ +""" +This module defines the FastAPI application instance and configures its middleware, +including CORS and router setup. The application serves as the entry point for the API. +""" + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.clients.router import router as clients_router - +# Create FastAPI application instance app = FastAPI() # Set API endpoints on router @@ -15,4 +20,4 @@ allow_origins=["*"], # Allows all origins allow_methods=["*"], # Allows all methods, including OPTIONS allow_headers=["*"], # Allows all headers -) \ No newline at end of file +) From a9e4350c4e6560195cedac10e6252e23ace4788e Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Sat, 23 Nov 2024 20:41:41 -0800 Subject: [PATCH 16/47] edit pylint --- .github/workflows/pylint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 3ea6feb5..0dd63b11 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -17,6 +17,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -r requirements.txt pip install pylint - name: Analysing the code with pylint run: | From d89a10c32d64efee9c4afdb6b79b8390d063699c Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Sat, 23 Nov 2024 20:46:21 -0800 Subject: [PATCH 17/47] Updated router.py --- app/clients/router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index a3ce0edb..d0319214 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -52,7 +52,7 @@ async def create_client(client_data: Client): Returns: Client: The created client object. """ - client_dict = client_data.dict(exclude={"id"}) + client_dict = client_data.dict(exclude={"id"}) for key, value in client_dict.items(): if isinstance(value, date): client_dict[key] = datetime.combine(value, datetime.min.time()) @@ -118,7 +118,7 @@ async def delete_client_by_id(client_id: str): delete_result = await clients_collection.delete_one({"_id": ObjectId(client_id)}) if delete_result.deleted_count == 0: raise HTTPException(status_code=404, detail="Client not found") - + return {"message": f"Client with ID {client_id} deleted successfully."} @router.delete("/clients", response_model=None, summary="Delete all clients") async def delete_all_clients(): From efcec6187c7d534d2d5876b4ab032d66d357d171 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Sat, 23 Nov 2024 20:47:34 -0800 Subject: [PATCH 18/47] edit pylint --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 0dd63b11..4a94dcaf 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From fb4e2dcee37ee40a9051d89b1a5ab0ee20ddfe75 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Sat, 23 Nov 2024 20:51:28 -0800 Subject: [PATCH 19/47] edit pylint and requirement --- .github/workflows/pylint.yml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 4a94dcaf..fe8a7362 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11"] + python-version: ["3.10"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} diff --git a/requirements.txt b/requirements.txt index 5f25b07f..7656ac01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -70,7 +70,7 @@ nbformat==5.8.0 nest-asyncio==1.5.6 notebook==6.5.4 notebook_shim==0.2.2 -numpy==24.3.1 +numpy==1.24.3 packaging==23.2 pandas==2.0.0 pandocfilters==1.5.0 From db3c3e2eea15ba166d11017d1ab1cb82c2b837f4 Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Sat, 23 Nov 2024 20:56:18 -0800 Subject: [PATCH 20/47] Updated model.py --- app/clients/service/model.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/clients/service/model.py b/app/clients/service/model.py index 6ded7829..db8177ba 100644 --- a/app/clients/service/model.py +++ b/app/clients/service/model.py @@ -49,8 +49,4 @@ def prepare_models(): 'employer_financial_supports', 'enhanced_referrals', ] - categorical_cols.extend(interventions) - - # Prepare training data - x_categorical_baseline = backend_code[categorical_cols] # Variable renamed to snake_case - x_categorical_baseline + categorical_cols.extend(interventions) \ No newline at end of file From 1bdac78f9c7604e93fd08cdf04c97e84d55d3426 Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Sat, 23 Nov 2024 21:01:56 -0800 Subject: [PATCH 21/47] Second update on model.py --- app/clients/service/model.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/clients/service/model.py b/app/clients/service/model.py index db8177ba..9ac29a3d 100644 --- a/app/clients/service/model.py +++ b/app/clients/service/model.py @@ -2,16 +2,10 @@ This module prepares and trains a machine learning model using the dataset, and saves the trained model for future use. """ - -import pandas as pd - - def prepare_models(): """ Prepares and trains a RandomForestRegressor model using the dataset. """ - # Load dataset and define the features and labels - backend_code = pd.read_csv('data_commontool.csv') # Variable renamed to snake_case # Define categorical columns and interventions categorical_cols = [ 'age', @@ -49,4 +43,4 @@ def prepare_models(): 'employer_financial_supports', 'enhanced_referrals', ] - categorical_cols.extend(interventions) \ No newline at end of file + categorical_cols.extend(interventions) From 145447ec24e0e0b48b851222eef73fa3274c5d96 Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Sat, 23 Nov 2024 21:07:18 -0800 Subject: [PATCH 22/47] made some updated to logic.py --- app/clients/service/logic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/clients/service/logic.py b/app/clients/service/logic.py index 11e52a06..079f6fb2 100644 --- a/app/clients/service/logic.py +++ b/app/clients/service/logic.py @@ -7,7 +7,10 @@ import pickle from itertools import product -import numpy as np # Ensure numpy is installed via `pip install numpy` +try: + import numpy as np # Ensure numpy is installed via `pip install numpy` +except ImportError as e: + raise ImportError("numpy is required but not installed. Install it using `pip install numpy`.") from e # Columns representing possible interventions column_intervention = [ @@ -61,7 +64,6 @@ def convert_text(data): Returns: int: Converted numerical value. """ - # TODO: Ensure that categorical columns match valid answers in FormNew.jsx (L131) categorical_cols_integers = [ {"": 0, "true": 1, "false": 0, "no": 0, "yes": 1}, { From e6b1ba05a5fd60c811435f522538ea421c5f7029 Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Sat, 23 Nov 2024 21:09:12 -0800 Subject: [PATCH 23/47] made some updated to logic.py --- app/clients/service/logic.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/clients/service/logic.py b/app/clients/service/logic.py index 079f6fb2..45352a8e 100644 --- a/app/clients/service/logic.py +++ b/app/clients/service/logic.py @@ -10,7 +10,9 @@ try: import numpy as np # Ensure numpy is installed via `pip install numpy` except ImportError as e: - raise ImportError("numpy is required but not installed. Install it using `pip install numpy`.") from e + raise ImportError( + "numpy is required but not installed. Install it using `pip install numpy`." + ) from e # Columns representing possible interventions column_intervention = [ @@ -168,9 +170,11 @@ def interpret_and_calculate(data): print("Running predictions...") sample_data = { "age": "23", "gender": "1", "work_experience": "1", "canada_workex": "1", "dep_num": "0", - "canada_born": "1", "citizen_status": "2", "level_of_schooling": "2", "fluent_english": "3", + "canada_born": "1", "citizen_status": "2", "level_of_schooling": "2", "fluent_english": + "3", "reading_english_scale": "2", "speaking_english_scale": "2", "writing_english_scale": "3", - "numeracy_scale": "2", "computer_scale": "3", "transportation_bool": "2", "caregiver_bool": "1", + "numeracy_scale": "2", "computer_scale": "3", "transportation_bool": "2", "caregiver_bool": + "1", "housing": "1", "income_source": "5", "felony_bool": "1", "attending_school": "0", "currently_employed": "1", "substance_use": "1", "time_unemployed": "1", "need_mental_health_support_bool": "1" From b6342b9655fa99f9af3e6e5a5bde430cc0ca09bd Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 20:58:11 -0800 Subject: [PATCH 24/47] add docker files --- .github/workflows/docker-build-test.yml | 39 +++++++++++++++++++ .github/workflows/docker.yml | 52 +++++++++++++++++++++++++ Dockerfile | 30 ++++++++++++++ app/.env | 4 +- app/config.py | 4 +- 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/docker-build-test.yml create mode 100644 .github/workflows/docker.yml create mode 100644 Dockerfile diff --git a/.github/workflows/docker-build-test.yml b/.github/workflows/docker-build-test.yml new file mode 100644 index 00000000..1f526a43 --- /dev/null +++ b/.github/workflows/docker-build-test.yml @@ -0,0 +1,39 @@ +name: Docker CI Pipeline + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker + uses: docker/setup-buildx-action@v2 + + - name: Build Docker Image + run: | + docker build -t fastapi-app . + + - name: Run Docker Container + run: | + docker run -d --name fastapi-container -p 8000:8000 fastapi-app + + - name: Test API Endpoints + run: | + sleep 5 # Allow some time for the container to start + curl -f http://localhost:8000/docs # Test FastAPI Swagger UI + curl -f http://localhost:8000/clients # Test a sample endpoint + + - name: Cleanup + run: | + docker stop fastapi-container + docker rm fastapi-container diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..6d955e9b --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,52 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout the repository + - name: Checkout repository + uses: actions/checkout@v4 + + # Step 2: Set up Docker + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + # Step 3: Log in to DockerHub (Optional) + # Replace DOCKER_USERNAME and DOCKER_PASSWORD secrets with your credentials + - name: Log in to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # Step 4: Build the Docker image + - name: Build Docker image + run: | + docker build -t fastapi-app:latest . + + # Step 5: Run the container + - name: Run the container + run: | + docker-compose up -d + + # Step 6: Test the container + - name: Test API health + run: | + sleep 10 # Wait for container to start + curl --fail http://localhost:8000/docs + + # Step 7: Push Docker image to DockerHub (Optional) + - name: Push Docker image to DockerHub + run: | + docker tag fastapi-app:latest ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest + docker push ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a4930ada --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Use an official Python runtime as a parent image +FROM python:3.10-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Set the working directory in the container +WORKDIR /app + +# Install system dependencies required for psutil and other packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + python3-dev \ + libffi-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install dependencies +COPY requirements.txt /app/requirements.txt +RUN pip install --no-cache-dir --upgrade pip && pip install -r requirements.txt + +# Copy the FastAPI app into the container +COPY . /app + +# Expose port 8000 for FastAPI +EXPOSE 8000 + +# Command to run the FastAPI server +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/app/.env b/app/.env index fe521abb..5c29d7df 100644 --- a/app/.env +++ b/app/.env @@ -1,2 +1,2 @@ -MONGODB_URI = mongodb+srv://team:IOi4XgpRxWVkKLv2@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 -MONGODB_NAME = Cluster0 +MONGODB_URI=mongodb+srv://team:IOi4XgpRxWVkKLv2@cluster0.mcjdj.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 +MONGODB_NAME=Cluster0 diff --git a/app/config.py b/app/config.py index 1bbfc3df..faa96e16 100644 --- a/app/config.py +++ b/app/config.py @@ -2,7 +2,7 @@ This module contains the configuration settings for the application. It uses Pydantic's BaseSettings to load settings from environment variables. """ - +from typing import ClassVar from pydantic_settings import BaseSettings from pydantic import BaseModel @@ -24,7 +24,7 @@ class Config(BaseModel): Attributes: env_file (str): Path to the .env file. """ - env_file = ".env" + env_file: ClassVar[str] = ".env" # Instantiate the settings object to be used throughout the application settings = Settings() From 1a8a94b74c743898eaa3c8b192216808bc399d27 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 21:16:20 -0800 Subject: [PATCH 25/47] Fix Docker login issue --- .github/workflows/docker.yml | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6d955e9b..1fecd80c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: CI/CD Pipeline +name: Docker CI Pipeline on: push: @@ -13,40 +13,40 @@ jobs: runs-on: ubuntu-latest steps: - # Step 1: Checkout the repository - - name: Checkout repository + # Step 1: Check out the code + - name: Checkout code uses: actions/checkout@v4 - # Step 2: Set up Docker - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - # Step 3: Log in to DockerHub (Optional) - # Replace DOCKER_USERNAME and DOCKER_PASSWORD secrets with your credentials + # Step 2: Log in to DockerHub - name: Log in to DockerHub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - # Step 4: Build the Docker image - - name: Build Docker image + # Step 3: Set up Docker + - name: Set up Docker + uses: docker/setup-buildx-action@v2 + + # Step 4: Build Docker image + - name: Build Docker Image run: | - docker build -t fastapi-app:latest . + docker build -t fastapi-app . - # Step 5: Run the container - - name: Run the container + # Step 5: Push Docker Image (Optional) + - name: Push Docker Image run: | - docker-compose up -d + docker tag fastapi-app ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest + docker push ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest - # Step 6: Test the container - - name: Test API health + # Step 6: Run the container + - name: Run Docker Container run: | - sleep 10 # Wait for container to start - curl --fail http://localhost:8000/docs + docker run -d --name fastapi-container -p 8000:8000 ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest - # Step 7: Push Docker image to DockerHub (Optional) - - name: Push Docker image to DockerHub + # Step 7: Test API Endpoints + - name: Test API Endpoints run: | - docker tag fastapi-app:latest ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest - docker push ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest + sleep 5 # Allow time for the container to start + curl -f http://localhost:8000/docs + curl -f http://localhost:8000/clients From 61153bda34670aa22056f17c8051b978d49ad546 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 21:28:59 -0800 Subject: [PATCH 26/47] Use GitHub secrets for MongoDB configuration --- .github/workflows/docker.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1fecd80c..f211f465 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # Step 2: Log in to DockerHub + # Step 2: Log in to DockerHub (Optional, if pushing the image) - name: Log in to DockerHub uses: docker/login-action@v2 with: @@ -33,18 +33,16 @@ jobs: run: | docker build -t fastapi-app . - # Step 5: Push Docker Image (Optional) - - name: Push Docker Image - run: | - docker tag fastapi-app ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest - docker push ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest - - # Step 6: Run the container + # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run -d --name fastapi-container -p 8000:8000 ${{ secrets.DOCKER_USERNAME }}/fastapi-app:latest + docker run -d \ + -p 8000:8000 \ + -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ + -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ + fastapi-app - # Step 7: Test API Endpoints + # Step 6: Test API Endpoints - name: Test API Endpoints run: | sleep 5 # Allow time for the container to start From 0d13a3b55fbfacc9a183fd5cff251d2ae85290fc Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 21:37:39 -0800 Subject: [PATCH 27/47] fix --- .github/workflows/docker.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f211f465..df1e58d0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,11 +36,7 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run -d \ - -p 8000:8000 \ - -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ - -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ - fastapi-app + docker run -d -p 8000:8000 -e MONGODB_URI=${{ secrets.MONGODB_URI }} -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} fastapi-app # Step 6: Test API Endpoints - name: Test API Endpoints From cdfbd2c37793ecadb2c1aebceb4bd4fc19b44203 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 21:51:12 -0800 Subject: [PATCH 28/47] fix --- .github/workflows/docker.yml | 5 ++++- .github/workflows/pylint.yml | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index df1e58d0..51997f63 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,10 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run -d -p 8000:8000 -e MONGODB_URI=${{ secrets.MONGODB_URI }} -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} fastapi-app + docker run -d -p 8000:8000 \ + -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ + -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ + fastapi-app # Step 6: Test API Endpoints - name: Test API Endpoints diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index fe8a7362..f098734f 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -22,4 +22,3 @@ jobs: - name: Analysing the code with pylint run: | pylint ./app; # pylint $(git ls-files '*.py') - From c2ee3bffbed56f9e0ab7e99f9b97a80337aee1b6 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 21:54:48 -0800 Subject: [PATCH 29/47] fix --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 51997f63..4bba18db 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run -d -p 8000:8000 \ + docker run --name -d -p 8000:8000 \ -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ fastapi-app From f2347321d633dd0983b83aeb35d85b24b868cc3b Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 21:59:14 -0800 Subject: [PATCH 30/47] fix --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4bba18db..7507d161 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run --name -d -p 8000:8000 \ + docker run --name fastapi-container -d -p 8000:8000 \ -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ fastapi-app From 87ed2a941ed1d02c3548530eb48e1d7b72794f9a Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:03:59 -0800 Subject: [PATCH 31/47] fix --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7507d161..111a3f59 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,10 +36,10 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run --name fastapi-container -d -p 8000:8000 \ + docker run --name nostalgic_bartik -d -p 8000:8000 \ -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ - fastapi-app + fastapi-app:latest # Step 6: Test API Endpoints - name: Test API Endpoints From f678e178c13c5be861b68df9a12e79fe0396ac16 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:10:18 -0800 Subject: [PATCH 32/47] a --- .github/workflows/docker.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 111a3f59..e97b7dd5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,10 +36,7 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run --name nostalgic_bartik -d -p 8000:8000 \ - -e MONGODB_URI=${{ secrets.MONGODB_URI }} \ - -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} \ - fastapi-app:latest + docker run --name nostalgic_bartik -d -p 8000:8000 -e MONGODB_URI=${{ secrets.MONGODB_URI }} -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} fastapi-app:latest # Step 6: Test API Endpoints - name: Test API Endpoints From 526a7f1ea75119cc87401af43dc1cee29f60f6bc Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:13:35 -0800 Subject: [PATCH 33/47] a --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e97b7dd5..bc5bd70c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run --name nostalgic_bartik -d -p 8000:8000 -e MONGODB_URI=${{ secrets.MONGODB_URI }} -e MONGODB_NAME=${{ secrets.MONGODB_NAME }} fastapi-app:latest + docker run --name nostalgic_bartik -d -p 8000:8000 -e MONGODB_URI='${{ secrets.MONGODB_URI }}' -e MONGODB_NAME='${{ secrets.MONGODB_NAME }}' fastapi-app:latest # Step 6: Test API Endpoints - name: Test API Endpoints From bd49f8797e7e57d9f06677a5bfaf8813af670863 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:17:31 -0800 Subject: [PATCH 34/47] fix --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bc5bd70c..646c2743 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: # Step 5: Run Docker Container with GitHub Secrets - name: Run Docker Container run: | - docker run --name nostalgic_bartik -d -p 8000:8000 -e MONGODB_URI='${{ secrets.MONGODB_URI }}' -e MONGODB_NAME='${{ secrets.MONGODB_NAME }}' fastapi-app:latest + docker run -d -p 8000:8000 -e MONGODB_URI='${{ secrets.MONGODB_URI }}' -e MONGODB_NAME='${{ secrets.MONGODB_NAME }}' fastapi-app:latest # Step 6: Test API Endpoints - name: Test API Endpoints From 833f41efb6ccb2b059bf59f28d4b4814905c61ee Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:23:59 -0800 Subject: [PATCH 35/47] fix --- .github/workflows/docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 646c2743..cebe884d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,4 +43,3 @@ jobs: run: | sleep 5 # Allow time for the container to start curl -f http://localhost:8000/docs - curl -f http://localhost:8000/clients From d0525b6a0f468e5bca83ae5f64db85bdca9edc06 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:27:29 -0800 Subject: [PATCH 36/47] fix --- .github/workflows/docker-build-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker-build-test.yml b/.github/workflows/docker-build-test.yml index 1f526a43..013f1448 100644 --- a/.github/workflows/docker-build-test.yml +++ b/.github/workflows/docker-build-test.yml @@ -31,7 +31,6 @@ jobs: run: | sleep 5 # Allow some time for the container to start curl -f http://localhost:8000/docs # Test FastAPI Swagger UI - curl -f http://localhost:8000/clients # Test a sample endpoint - name: Cleanup run: | From 740f2fb35107fdf0d67366dcf64ec534c4b39b98 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 26 Nov 2024 22:31:55 -0800 Subject: [PATCH 37/47] fix --- .github/workflows/docker-build-test.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/docker-build-test.yml b/.github/workflows/docker-build-test.yml index 013f1448..2763f841 100644 --- a/.github/workflows/docker-build-test.yml +++ b/.github/workflows/docker-build-test.yml @@ -27,11 +27,6 @@ jobs: run: | docker run -d --name fastapi-container -p 8000:8000 fastapi-app - - name: Test API Endpoints - run: | - sleep 5 # Allow some time for the container to start - curl -f http://localhost:8000/docs # Test FastAPI Swagger UI - - name: Cleanup run: | docker stop fastapi-container From 23c9f5c5334871066ff3ef0c1caed82433ec3354 Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Sun, 1 Dec 2024 22:59:59 -0800 Subject: [PATCH 38/47] Added tests for update_client method. Updated config.py to make sure .env file is read properly. --- app/clients/router.py | 2 +- app/config.py | 14 ++++-- requirements.txt | 1 + tests/test.py | 100 +++++++++++++++++++++++++++++++++++------- 4 files changed, 95 insertions(+), 22 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index d0319214..00c0c26f 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -56,7 +56,7 @@ async def create_client(client_data: Client): for key, value in client_dict.items(): if isinstance(value, date): client_dict[key] = datetime.combine(value, datetime.min.time()) - result = await clients_collection.insert_one(client_dict) + result = await clients_collection.insert_one(client_dict) client_dict["_id"] = result.inserted_id return Client(id=str(client_dict["_id"]), **client_dict) diff --git a/app/config.py b/app/config.py index faa96e16..6d0119b9 100644 --- a/app/config.py +++ b/app/config.py @@ -2,9 +2,13 @@ This module contains the configuration settings for the application. It uses Pydantic's BaseSettings to load settings from environment variables. """ -from typing import ClassVar +import os from pydantic_settings import BaseSettings -from pydantic import BaseModel +from dotenv import load_dotenv + +# Explicitly load the .env file +dotenv_path = os.path.join(os.path.dirname(__file__), '../app/', '.env') +load_dotenv(dotenv_path) class Settings(BaseSettings): """ @@ -17,14 +21,16 @@ class Settings(BaseSettings): MONGODB_URI: str MONGODB_NAME: str - class Config(BaseModel): + class Config: """ Config class to specify Pydantic settings behavior. Attributes: env_file (str): Path to the .env file. """ - env_file: ClassVar[str] = ".env" + env_file = ".env" + env_file_encoding = "utf-8" + # Instantiate the settings object to be used throughout the application settings = Settings() diff --git a/requirements.txt b/requirements.txt index 7656ac01..1971d908 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,6 +34,7 @@ folium==0.14.0 fqdn==1.5.1 h11==0.14.0 httptools==0.6.0 +httpx==0.28.0 idna==3.4 iniconfig==1.1.1 ipykernel==6.22.0 diff --git a/tests/test.py b/tests/test.py index a911f0a2..417787e7 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,23 +1,89 @@ -from logic import interpret_and_calculate -from itertools import combinations_with_replacement +""" +This module contains tests for the client update functionality in the FastAPI application. +""" -# def test_interpret_and_calculate(): -# print("running tests") -# data = {"23","1","1","1","1","0","1","2","2","3","2", -# "2","3","2","1","1","1","1","1","1","0","1","1","1" -# } -# result = interpret_and_calculate(data) -# print(data) +from unittest.mock import AsyncMock +from datetime import date, datetime +from bson import ObjectId +from fastapi import HTTPException +import pytest +from app.clients.schema import ClientUpdate +from app.clients.router import update_client +from app.database import clients_collection -from itertools import product +@pytest.mark.asyncio +async def test_update_client(): + """ + Test the update_client function to ensure it correctly updates a client's information. + """ + # Prepare test data + original_client_id = str(ObjectId()) + original_client_data = { + "_id": ObjectId(original_client_id), + "first_name": "John", + "last_name": "Doe", + "email": "johndoe@example.com", + "date_of_birth": date(1990, 1, 1), + "address": "123 Main St", + "phone": "123-456-7890" + } -# Cartesian product of [0, 1] repeated 2 times -result = list(product([0, 1], repeat=2)) + # Prepare update data + client_update = ClientUpdate( + first_name="Jane", + last_name="Doee", + email="jane.doee@example.com", + date_of_birth=datetime.combine(date(1991, 2, 2), datetime.min.time()), + address="456 Elm St", + phone="098-765-4321" + ) -# Output: [(0, 0), (0, 1), (1, 0), (1, 1)] -print(result) + clients_collection.find_one = AsyncMock(return_value=original_client_data) + clients_collection.update_one = AsyncMock() -result = list(combinations_with_replacement([0, 1], 2)) + # Call the update method + updated_client = await update_client(original_client_id, client_update) -# Output: [(0, 0), (0, 1), (1, 1)] -print(result) \ No newline at end of file + # Update the mock to return the updated data + updated_client_data = original_client_data.copy() + updated_client_data.update(client_update.dict()) + updated_client_data["_id"] = ObjectId(original_client_id) # Restore the original _id + + clients_collection.find_one = AsyncMock(return_value=updated_client_data) + + # Call the update method again to get the updated client + updated_client = await update_client(original_client_id, client_update) + + # Assertions + assert updated_client["first_name"] == "Jane" + assert updated_client["last_name"] == "Doee" + assert updated_client["email"] == "jane.doee@example.com" + assert updated_client["date_of_birth"] == datetime(1991, 2, 2).date() + assert updated_client["address"] == "456 Elm St" + assert updated_client["phone"] == "098-765-4321" + +@pytest.mark.asyncio +async def test_update_client_not_found(): + """ + Test the update_client function to ensure it raises an HTTPException + when the client is not found. + """ + # Prepare test data + non_existent_client_id = str(ObjectId()) + + # Mock the find_one method to return None + clients_collection.find_one = AsyncMock(return_value=None) + + # Prepare update data + client_update = ClientUpdate( + first_name="John Updated", + last_name="Doe" + ) + + # Expect HTTPException to be raised + with pytest.raises(HTTPException) as exc_info: + await update_client(non_existent_client_id, client_update) + + # Additional assertion on the exception + assert exc_info.value.status_code == 404 + assert exc_info.value.detail == "Client not found" From 7f598fad48d57f25496bb29627587bc40e430c13 Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 3 Dec 2024 19:15:23 +0800 Subject: [PATCH 39/47] add get tests --- app/clients/router.py | 34 +------------ tests/test.py | 115 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 33 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index 00c0c26f..28251929 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -13,34 +13,6 @@ router = APIRouter(prefix="/clients", tags=["clients"]) -mock_clients_db = { - 1: Client( - id="1", - first_name="Amy", - last_name="Doe", - email="amy.doe@example.com", - date_of_birth="1995-04-23", - address="123 Main St, Springfield", - phone="123-456-7890" - ), - 2: Client( - id="2", - first_name="Bob", - last_name="Smith", - email="bob.smith@example.com", - date_of_birth="1999-08-17", - address="456 Elm St, Springfield", - phone="098-765-4321" - ), -} - -def generate_new_id(): - """ - Generate a new unique ID for a client. - """ - return max(mock_clients_db.keys(), default=0) + 1 - - @router.post("/create", response_model=Client, summary="Create a new client") async def create_client(client_data: Client): """ @@ -79,7 +51,6 @@ async def get_client_by_id(client_id: str): del client["_id"] return client - @router.get("/clients", response_model=List[Client], summary="Retrieve all clients") async def get_all_clients(): """ @@ -88,7 +59,7 @@ async def get_all_clients(): Returns: List[Client]: A list of all clients. """ - clients_cursor = await clients_collection.find().to_list(length=100) + clients_cursor = list(await clients_collection.find()) # Convert _id to id and return the list for client in clients_cursor: client["id"] = str(client["_id"]) @@ -96,7 +67,6 @@ async def get_all_clients(): return clients_cursor - @router.delete("/clients/{client_id}", response_model=None, summary="Delete client by ID") async def delete_client_by_id(client_id: str): """ @@ -120,6 +90,7 @@ async def delete_client_by_id(client_id: str): raise HTTPException(status_code=404, detail="Client not found") return {"message": f"Client with ID {client_id} deleted successfully."} + @router.delete("/clients", response_model=None, summary="Delete all clients") async def delete_all_clients(): """ @@ -131,7 +102,6 @@ async def delete_all_clients(): await clients_collection.delete_many({}) return {"message": "All clients deleted successfully."} - @router.put("/clients/{client_id}", response_model=Client, summary="Update client by ID") async def update_client(client_id: str, client_data: ClientUpdate): """ diff --git a/tests/test.py b/tests/test.py index 417787e7..7e54e614 100644 --- a/tests/test.py +++ b/tests/test.py @@ -8,9 +8,122 @@ from fastapi import HTTPException import pytest from app.clients.schema import ClientUpdate -from app.clients.router import update_client +from app.clients.router import get_client_by_id, get_all_clients, update_client from app.database import clients_collection +@pytest.mark.asyncio +async def test_get_client_by_id(): + """ + Test the get_client_by_id function to ensure it retrieves a client correctly. + """ + + client_id = str(ObjectId()) # Generate a test client ID + client_data = { + "_id": ObjectId(client_id), + "first_name": "John", + "last_name": "Doe", + "email": "john.doe@example.com", + "date_of_birth": "1990-01-15", + "address": "123 Elm St, Springfield, IL", + "phone": "555-1234" + } + + # Mock the database method to return the sample client data + clients_collection.find_one = AsyncMock(return_value=client_data) + + # Call the handler directly instead of using TestClient + client = await get_client_by_id(client_id) # Directly calling the handler + + # Perform assertions + assert client["id"] == client_id + assert client["first_name"] == "John" + assert client["last_name"] == "Doe" + assert client["email"] == "john.doe@example.com" + assert client["date_of_birth"] == "1990-01-15" + assert client["address"] == "123 Elm St, Springfield, IL" + assert client["phone"] == "555-1234" + +@pytest.mark.asyncio +async def test_get_client_by_id_not_found(): + """ + Test the get_client_by_id function to ensure it raises an HTTPException + when the client is not found. + """ + + non_existent_client_id = str(ObjectId()) + + # Mock the find_one method to return None for a non-existent client + clients_collection.find_one = AsyncMock(return_value=None) + + # Call the handler directly + try: + await get_client_by_id(non_existent_client_id) + pytest.fail("Expected HTTPException not raised") + except HTTPException as e: + assert e.status_code == 404 + assert e.detail == "Client not found" + +@pytest.mark.asyncio +async def test_get_all_clients(): + """ + Test the get_all_clients function to ensure it retrieves all clients correctly. + """ + # Sample client data + client_data = [ + { + "_id": ObjectId(), + "first_name": "John", + "last_name": "Doe", + "email": "john.doe@example.com", + "date_of_birth": "1990-01-15", + "address": "123 Elm St, Springfield, IL", + "phone": "555-1234" + }, + { + "_id": ObjectId(), + "first_name": "Jane", + "last_name": "Smith", + "email": "jane.smith@example.com", + "date_of_birth": "1985-06-22", + "address": "456 Oak St, Chicago, IL", + "phone": "555-5678" + } + ] + + # Mocking the database find method to return an async iterable + async def mock_find(*args, **kwargs): + return iter(client_data) # Returning an async iterable (simulating a cursor) + + # Mock the `find` method + clients_collection.find = AsyncMock(side_effect=mock_find) + + # Call the function and await the result + clients = await get_all_clients() # Awaiting to get the actual list + + # Perform assertions + assert len(clients) == 2 # Check the length of the returned list + assert clients[0]["first_name"] == "John" + assert clients[1]["first_name"] == "Jane" + +@pytest.mark.asyncio +async def test_get_all_clients_empty(): + """ + Test the get_all_clients function to ensure it returns an empty list + when no clients are found. + """ + # Mocking the find method to return an empty async iterable + async def mock_find_empty(*args, **kwargs): + return iter([]) # Return an empty async iterable + + # Mock the `find` method + clients_collection.find = AsyncMock(side_effect=mock_find_empty) + + # Call the function and await the result + clients = await get_all_clients() # Awaiting to get the actual list + + # Perform assertions + assert clients == [] + @pytest.mark.asyncio async def test_update_client(): """ From ea92356d1ec70c61ea2299df79906c301d50875a Mon Sep 17 00:00:00 2001 From: Itsc2y Date: Tue, 3 Dec 2024 19:32:15 +0800 Subject: [PATCH 40/47] add bypass --- app/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/config.py b/app/config.py index 6d0119b9..31ef3af1 100644 --- a/app/config.py +++ b/app/config.py @@ -10,6 +10,7 @@ dotenv_path = os.path.join(os.path.dirname(__file__), '../app/', '.env') load_dotenv(dotenv_path) +# pylint: disable=too-few-public-methods class Settings(BaseSettings): """ Settings class to manage application configuration. From 5e35f1cfe4445dc02f6c8d4497c28f215196e7bc Mon Sep 17 00:00:00 2001 From: Joyce Lynn Date: Tue, 3 Dec 2024 10:31:35 -0800 Subject: [PATCH 41/47] edited test.py --- tests/test.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test.py b/tests/test.py index 7e54e614..0ce67d7f 100644 --- a/tests/test.py +++ b/tests/test.py @@ -11,6 +11,39 @@ from app.clients.router import get_client_by_id, get_all_clients, update_client from app.database import clients_collection +@pytest.mark.asyncio +async def test_create_client(): + """ + Test the create_client function to ensure it creates a new client in the database correctly. + """ + # Prepare test data + test_client_data = { + "first_name": "Alice", + "last_name": "Smith", + "email": "alice.smith@example.com", + "date_of_birth": date(1992, 5, 10), + "address": "789 Birch St, Springfield, IL", + "phone": "555-6789" + } + + # Mock the insert_one method to return a mock inserted_id + mock_inserted_id = ObjectId() + clients_collection.insert_one = AsyncMock(return_value=AsyncMock(inserted_id=mock_inserted_id)) + + # Call the function with test data + from app.clients.schema import Client + test_client = Client(**test_client_data) + created_client = await create_client(test_client) + + # Assertions + assert created_client.id == str(mock_inserted_id) + for key, value in test_client_data.items(): + if isinstance(value, date): + assert created_client.dict()[key] == datetime.combine(value, datetime.min.time()).date() + else: + assert created_client.dict()[key] == value + + @pytest.mark.asyncio async def test_get_client_by_id(): """ From dc60440671a2feaac67d8d7099b15d31319ebff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Tue, 3 Dec 2024 12:49:01 -0800 Subject: [PATCH 42/47] Added tests for delete functions --- app/clients/router.py | 9 ++- tests/test.py | 140 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 141 insertions(+), 8 deletions(-) diff --git a/app/clients/router.py b/app/clients/router.py index 28251929..d50fa328 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -13,6 +13,7 @@ router = APIRouter(prefix="/clients", tags=["clients"]) + @router.post("/create", response_model=Client, summary="Create a new client") async def create_client(client_data: Client): """ @@ -32,6 +33,7 @@ async def create_client(client_data: Client): client_dict["_id"] = result.inserted_id return Client(id=str(client_dict["_id"]), **client_dict) + @router.get("/clients/{client_id}", response_model=Client, summary="Retrieve client by ID") async def get_client_by_id(client_id: str): """ @@ -51,6 +53,7 @@ async def get_client_by_id(client_id: str): del client["_id"] return client + @router.get("/clients", response_model=List[Client], summary="Retrieve all clients") async def get_all_clients(): """ @@ -67,6 +70,7 @@ async def get_all_clients(): return clients_cursor + @router.delete("/clients/{client_id}", response_model=None, summary="Delete client by ID") async def delete_client_by_id(client_id: str): """ @@ -91,6 +95,7 @@ async def delete_client_by_id(client_id: str): return {"message": f"Client with ID {client_id} deleted successfully."} + @router.delete("/clients", response_model=None, summary="Delete all clients") async def delete_all_clients(): """ @@ -102,6 +107,7 @@ async def delete_all_clients(): await clients_collection.delete_many({}) return {"message": "All clients deleted successfully."} + @router.put("/clients/{client_id}", response_model=Client, summary="Update client by ID") async def update_client(client_id: str, client_data: ClientUpdate): """ @@ -122,7 +128,8 @@ async def update_client(client_id: str, client_data: ClientUpdate): updated_fields = client_data.dict(exclude_unset=True) for field, value in updated_fields.items(): if isinstance(value, date): - updated_fields[field] = datetime.combine(value, datetime.min.time()) + updated_fields[field] = datetime.combine( + value, datetime.min.time()) await clients_collection.update_one({"_id": ObjectId(client_id)}, {"$set": updated_fields}) diff --git a/tests/test.py b/tests/test.py index 0ce67d7f..b673ebbf 100644 --- a/tests/test.py +++ b/tests/test.py @@ -7,10 +7,11 @@ from bson import ObjectId from fastapi import HTTPException import pytest -from app.clients.schema import ClientUpdate +from app.clients.schema import ClientUpdate, Client from app.clients.router import get_client_by_id, get_all_clients, update_client from app.database import clients_collection + @pytest.mark.asyncio async def test_create_client(): """ @@ -28,10 +29,10 @@ async def test_create_client(): # Mock the insert_one method to return a mock inserted_id mock_inserted_id = ObjectId() - clients_collection.insert_one = AsyncMock(return_value=AsyncMock(inserted_id=mock_inserted_id)) + clients_collection.insert_one = AsyncMock( + return_value=AsyncMock(inserted_id=mock_inserted_id)) # Call the function with test data - from app.clients.schema import Client test_client = Client(**test_client_data) created_client = await create_client(test_client) @@ -39,7 +40,8 @@ async def test_create_client(): assert created_client.id == str(mock_inserted_id) for key, value in test_client_data.items(): if isinstance(value, date): - assert created_client.dict()[key] == datetime.combine(value, datetime.min.time()).date() + assert created_client.dict()[key] == datetime.combine( + value, datetime.min.time()).date() else: assert created_client.dict()[key] == value @@ -76,13 +78,14 @@ async def test_get_client_by_id(): assert client["address"] == "123 Elm St, Springfield, IL" assert client["phone"] == "555-1234" + @pytest.mark.asyncio async def test_get_client_by_id_not_found(): """ Test the get_client_by_id function to ensure it raises an HTTPException when the client is not found. """ - + non_existent_client_id = str(ObjectId()) # Mock the find_one method to return None for a non-existent client @@ -96,6 +99,7 @@ async def test_get_client_by_id_not_found(): assert e.status_code == 404 assert e.detail == "Client not found" + @pytest.mark.asyncio async def test_get_all_clients(): """ @@ -125,7 +129,8 @@ async def test_get_all_clients(): # Mocking the database find method to return an async iterable async def mock_find(*args, **kwargs): - return iter(client_data) # Returning an async iterable (simulating a cursor) + # Returning an async iterable (simulating a cursor) + return iter(client_data) # Mock the `find` method clients_collection.find = AsyncMock(side_effect=mock_find) @@ -138,6 +143,7 @@ async def mock_find(*args, **kwargs): assert clients[0]["first_name"] == "John" assert clients[1]["first_name"] == "Jane" + @pytest.mark.asyncio async def test_get_all_clients_empty(): """ @@ -157,6 +163,7 @@ async def mock_find_empty(*args, **kwargs): # Perform assertions assert clients == [] + @pytest.mark.asyncio async def test_update_client(): """ @@ -193,7 +200,8 @@ async def test_update_client(): # Update the mock to return the updated data updated_client_data = original_client_data.copy() updated_client_data.update(client_update.dict()) - updated_client_data["_id"] = ObjectId(original_client_id) # Restore the original _id + updated_client_data["_id"] = ObjectId( + original_client_id) # Restore the original _id clients_collection.find_one = AsyncMock(return_value=updated_client_data) @@ -208,6 +216,7 @@ async def test_update_client(): assert updated_client["address"] == "456 Elm St" assert updated_client["phone"] == "098-765-4321" + @pytest.mark.asyncio async def test_update_client_not_found(): """ @@ -233,3 +242,120 @@ async def test_update_client_not_found(): # Additional assertion on the exception assert exc_info.value.status_code == 404 assert exc_info.value.detail == "Client not found" + + +@pytest.mark.asyncio +async def test_delete_client_by_id(): + """ + Test the delete_client_by_id function to ensure it deletes a client by ID correctly. + """ + # Prepare test data + test_client_id = str(ObjectId()) + client_data = { + "_id": ObjectId(test_client_id), + "first_name": "John", + "last_name": "Doe", + "email": "john.doe@example.com", + "date_of_birth": "1990-01-15", + "address": "123 Elm St, Springfield, IL", + "phone": "555-1234" + } + + # Mock find_one to simulate client exists + clients_collection.find_one = AsyncMock(return_value=client_data) + # Mock delete_one to simulate successful deletion + clients_collection.delete_one = AsyncMock( + return_value=AsyncMock(deleted_count=1)) + + # Call the delete function + response = await delete_client_by_id(test_client_id) + + # Assertions + assert response["message"] == f"Client with ID {test_client_id} deleted successfully." + + +@pytest.mark.asyncio +async def test_delete_client_by_id_not_found(): + """ + Test the delete_client_by_id function to ensure it raises HTTPException + when the client is not found. + """ + # Prepare test data + non_existent_client_id = str(ObjectId()) + + # Mock find_one to simulate client does not exist + clients_collection.find_one = AsyncMock(return_value=None) + + # Call the delete function and expect an HTTPException + with pytest.raises(HTTPException) as exc_info: + await delete_client_by_id(non_existent_client_id) + + # Assertions + assert exc_info.value.status_code == 404 + assert exc_info.value.detail == "Client not found" + + +@pytest.mark.asyncio +async def test_delete_client_by_id(): + """ + Test the delete_client_by_id function to ensure it deletes a client by ID correctly. + """ + # Prepare test data + test_client_id = str(ObjectId()) + client_data = { + "_id": ObjectId(test_client_id), + "first_name": "John", + "last_name": "Doe", + "email": "john.doe@example.com", + "date_of_birth": "1990-01-15", + "address": "123 Elm St, Springfield, IL", + "phone": "555-1234" + } + + # Mock find_one to simulate client exists + clients_collection.find_one = AsyncMock(return_value=client_data) + # Mock delete_one to simulate successful deletion + clients_collection.delete_one = AsyncMock( + return_value=AsyncMock(deleted_count=1)) + + # Call the delete function + response = await delete_client_by_id(test_client_id) + + # Assertions + assert response["message"] == f"Client with ID {test_client_id} deleted successfully." + + +@pytest.mark.asyncio +async def test_delete_client_by_id_not_found(): + """ + Test the delete_client_by_id function to ensure it raises HTTPException + when the client is not found. + """ + # Prepare test data + non_existent_client_id = str(ObjectId()) + + # Mock find_one to simulate client does not exist + clients_collection.find_one = AsyncMock(return_value=None) + + # Call the delete function and expect an HTTPException + with pytest.raises(HTTPException) as exc_info: + await delete_client_by_id(non_existent_client_id) + + # Assertions + assert exc_info.value.status_code == 404 + assert exc_info.value.detail == "Client not found" + + +@pytest.mark.asyncio +async def test_delete_all_clients(): + """ + Test the delete_all_clients function to ensure it deletes all clients in the database. + """ + # Mock delete_many to simulate successful deletion + clients_collection.delete_many = AsyncMock() + + # Call the delete function + response = await delete_all_clients() + + # Assertions + assert response["message"] == "All clients deleted successfully." From 7205a34533cb2d0c74ea41f653b5acbbd25930f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E6=B4=81?= Date: Tue, 3 Dec 2024 13:55:15 -0800 Subject: [PATCH 43/47] Covered all functions in router.py --- .coverage | Bin 0 -> 53248 bytes tests/{test.py => test_example.py} | 61 +++++++++++++++-------------- 2 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 .coverage rename tests/{test.py => test_example.py} (90%) diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..5c70aa1e46e6824cf2703c080e27f78a05961a58 GIT binary patch literal 53248 zcmeI4?~5D98OL|EtKE|8n!IF7^qPQiZMUZD+K;8wG_64IGQG24(~g-P z&{iMW-)gs@EuDS4EKX}B>vx*?W%;;REWKL(;jVW|-NJ8n{oZy8O*(-90w4eaAn^Sr zFgQ`Nt1~nD^Up<2qobn0X{sQJ%a1<3x^i+=o?Q9Ku~nI*$pb}6VR2Ee$iUx_Jr&3` zx1*%%wcMr?xn5gF>nh9L4^@jNI!dFdjx#RB<$15}wy0F3+9c5n+^!Q`l+UP(3vq_@ zYLU7SB?%;=+_uL#4j!f8p0fySjI;eo4i3a)Ktkc8%4B z5{6Ydx<*1xKWMS_jll7m>ndE3&SnjpKG}I8N@^SU-CE?!j_bwM^g=gseNU$(+OU}#w&c%!u;tutJqGfBzxS|fCZg|AaG&7KxsBjY?j!#XU;m!`t8&0fFTAmnHJ z-5xg%l}Iie`CFCTs%+$^mHpOjbDg#v)_1HqGvwT&NvXc)p6jd4#$S-bk_&M)JbyuQ zCCCHz@3N{7?$tH&N_8PT*P+i_b*CTs@o}9RzrIXS>BT)$c6HxA{nEMEQ5a9X;YQ)+ zPGQVxHj}q-9X`Lc-*7J(cM|SJosRU^;x5CbWzsn0&7*y$mceiW?r73aGm@n@)!W!u zvOzXFeq-F%Vvh+Gbr2d0vTSR8ugP5{qlT&E4W)#0E5`rJ0z1mj-=)8)GJO%FyJWtBZ^0oeqsAJ;x2G%hDK} z%m9)zwj9drH2i)P`!65H?#Xk{(B4E~c$~4PT=Q{u>fVp}Vd&Dd%F|Z(P?jp^)}4?? zp>R$%e7~a{FPR&t#L4q}4;o4d0k)cL-ku}=q=VF_($%Tjclho<5{zW_8uH&`R z`HH9EM3u&V?wov`WDuc(a*jG>NL}_ct9Re65Bh0m zOvagdNRxWk$;?g=svxW3a0KWfUEPbSjKZ%z3LFKQN zUsdiYe^!2}{4gaVfB*=900@8p2!H?xfB*=9zz!pDs%UD1TJ2Ow|0l0)I9@w)9q+8G zYKK+$Oyu`!hYlS)SbLQIo9=rnVW`5etGwtW{TaUI^m?^s$ECxtM#ay&O;zjqE!CmF znx86ct8B+_yG@dPa*AXRZJV|Ay6d@7y-v!9ZBi~ztlV{6k5qnYk;(vrC&#I7*0M~^7s`F|K7K>!3m00ck) z1V8`;KmY_l00cnbmL_26hF0eL|GM~}MlS>q009sH0T2KI5C8!X009sH0T2LzTabWZ zlnZ?S-x7b+#8=`A@xFLVyg>&DAOHd&00JNY0w4eaAOHd&00JNY0ympL(K7YIZPo{W z`?GbIou~cX`9By{lXec8ji;^8+uDZpdg;u@%q#i>^=ieS6L%PYnYnW1%5NX}(6(}P zT*>Kq-7FL;I^X}d#JigKTKq>`7N3as=l}r(KmY_l00ck)1V8`;KmY_l00cl_M-bre z2z1MeH~DM- zfbaif{XZc*6axVe009sH0T2KI5C8!X009sHfgM7C|NmbUf9K!-e@RaO_)Ppud?5ZQ z-WC6#L Date: Tue, 3 Dec 2024 18:33:50 -0800 Subject: [PATCH 44/47] fixed bug in get_all_clients --- .coverage | Bin 53248 -> 53248 bytes app/.coverage | Bin 0 -> 53248 bytes app/clients/router.py | 3 ++- runServer.sh | 0 tests/test_example.py | 47 ++++++++++++++++++++++-------------------- 5 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 app/.coverage mode change 100755 => 100644 runServer.sh diff --git a/.coverage b/.coverage index 5c70aa1e46e6824cf2703c080e27f78a05961a58..4bac2bc5788244ab3cee44726080e736e880749f 100644 GIT binary patch delta 1132 zcmZozz}&Eac|sCXw9&?tMfPG-82H!mhw%O3Tf!H_$HcpZH;Csm&y>xA0u?-(v0N++ zjrRJX#i>Qb`W2aZ=~J(~k|PT9G)_az@Gdd1;yH zKuZExf!b7w)HZpiAGc;03s8q4u{xOj1vSH%NzoAu)Dgi1)L}}j9gF>iH3Jw)@yAzx zL9wX}{OkEc`Ihqi{z@@T}dG}iOuscHDLG%6CO*bgazum!R-suHVh^3FIOjc}GmL!vb>#|x3BAsDD3 zlBLm^Ex zThWgNs8SK1%Cmm@ihj(5RPFUqpX}q$p%}mfRH=&3yjp)lMGr<|-1pUAQ0yrK|91XV zzN7r2ypwrtc%JeZ^HuY{1;*P+9)&QLMtw$nR_%>3Cqj`ABGxz~SsG388|ed#wOAen zSC&RzybABeswud$Gz#KXgdAsVo-B=`1XKg#ERILPpQTX|ufnr&280!XV=SIiA&{j} z6|ZTv@kR2vMkNhp_`!DtaLKKWt4I0qvuBMT=dALF744gjkX BK9v9f diff --git a/app/.coverage b/app/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..f7c727e77a58d4828bdb5ab099e18fcc8d9b5e5a GIT binary patch literal 53248 zcmeI)O>f&a7zc1EZmrmf;TAz41UZD4%`s}b@CNMluw`2g#R_!Yf?W#CrDZzNW=W1- z@^Tmkw*vwU*f$vVo%Tt3+G&?Pk0@Do5*NJ{Q2#>g=!K-nA0Lu-)%J&{kKI^_aS(bk z79Se-4AV5e6v8l!b$T`Em2Vd9+{q8y z4|3KbBj1`br*-xdZ%E`nC*G7hhjQo}$HA(ctMbM{*4Q`hn_K#_Xzx;Y=H%R?I#=Sm zr-_<(en%8JJ83igpd+{u6anvTRP9fS^I^ z2NH!&zgD#m_g00@JOl>)`EGr-Q?=~fUGqgP4Wq2LvR~Fx2<1I_%QTu_@|28{FrP?X zH%aC>idixrBiR*dPS>j12fM49lcgK+Vt`_)IYbt}O&_ zUj3u%Nle0x>Jxe9|mzgJ&ATT82awUg?5H0krJvQ%Y0Q&jm|b=f_W z6Pit?(hX_I(j2T;0Ko}Ik`l{dki=>H@?jdDy!I6J)ddvCxjm(7r^#t}e-}iNOUo)R zThV9vSC?_)oUq*j2OsJ?@Z_d@aHtjX zRM}^pdYixCo6Y|W`oRVP2tWV=5P$##AOHafKmY;|fI#^KD(0GL>F@uqjOMHIH4rU= z00bZa0SG_<0uX=z1Rwwb2)v^LO|!CVXMYLc=< zfBJ_F0uX=z1Rwwb2tWV=5P$##AOL|93*h;Gi8~ifg8&2|009U<00Izz00bZa0SIIQ zc>a%2fB*y_009U<00Izz00bZa0SJ^|0MGx+-^XYn1Rwwb2tWV=5P$##AOHafKmgDG z5d#o_00bZa0SG_<0uX=z1Rwx`@(bYkfBE|uErb9BAOHafKmY;|fB*y_009W#`9ER+ y0uX=z1Rwwb2tWV=5P$##AW(h*JpV6$AESj3fB*y_009U<00Izz00bZaf&T$PJQN53 literal 0 HcmV?d00001 diff --git a/app/clients/router.py b/app/clients/router.py index d50fa328..b4fedd31 100644 --- a/app/clients/router.py +++ b/app/clients/router.py @@ -62,7 +62,8 @@ async def get_all_clients(): Returns: List[Client]: A list of all clients. """ - clients_cursor = list(await clients_collection.find()) + # clients_cursor = list(await clients_collection.find()) + clients_cursor = await clients_collection.find().to_list(length=None) # Convert _id to id and return the list for client in clients_cursor: client["id"] = str(client["_id"]) diff --git a/runServer.sh b/runServer.sh old mode 100755 new mode 100644 diff --git a/tests/test_example.py b/tests/test_example.py index bd846237..cac07300 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -2,7 +2,7 @@ This module contains tests for the client update functionality in the FastAPI application. """ -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch from datetime import date, datetime from bson import ObjectId from fastapi import HTTPException @@ -128,21 +128,22 @@ async def test_get_all_clients(): } ] - # Mocking the database find method to return an async iterable - async def mock_find(*args, **kwargs): - # Returning an async iterable (simulating a cursor) - return iter(client_data) + # Mocking the async cursor with to_list method + class MockCursor: + async def to_list(self, length): + return client_data - # Mock the `find` method - clients_collection.find = AsyncMock(side_effect=mock_find) + mock_cursor = MockCursor() - # Call the function and await the result - clients = await get_all_clients() # Awaiting to get the actual list + # Mock the `find` method to return the mock_cursor + with patch('app.database.clients_collection.find', return_value=mock_cursor): + # Call the function and await the result + clients = await get_all_clients() - # Perform assertions - assert len(clients) == 2 # Check the length of the returned list - assert clients[0]["first_name"] == "John" - assert clients[1]["first_name"] == "Jane" + # Perform assertions + assert len(clients) == 2 # Check the length of the returned list + assert clients[0]["first_name"] == "John" + assert clients[1]["first_name"] == "Jane" @pytest.mark.asyncio @@ -151,18 +152,20 @@ async def test_get_all_clients_empty(): Test the get_all_clients function to ensure it returns an empty list when no clients are found. """ - # Mocking the find method to return an empty async iterable - async def mock_find_empty(*args, **kwargs): - return iter([]) # Return an empty async iterable + client_data = [] + class MockCursor: + async def to_list(self, length): + return client_data - # Mock the `find` method - clients_collection.find = AsyncMock(side_effect=mock_find_empty) + mock_cursor = MockCursor() - # Call the function and await the result - clients = await get_all_clients() # Awaiting to get the actual list + # Mock the `find` method to return the mock_cursor + with patch('app.database.clients_collection.find', return_value=mock_cursor): + # Call the function and await the result + clients = await get_all_clients() - # Perform assertions - assert clients == [] + # Perform assertions + assert clients == [] @pytest.mark.asyncio From 44cb7f04d25f915cf95381856b1d2604c7500746 Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Tue, 3 Dec 2024 18:51:41 -0800 Subject: [PATCH 45/47] Added pytest to pylint --- .github/workflows/pylint.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index f098734f..7041e703 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -18,7 +18,14 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pylint + pip install pylint pytest pytest-asyncio pytest-cov - name: Analysing the code with pylint run: | pylint ./app; # pylint $(git ls-files '*.py') + - name: Run tests with coverage + run: | + pytest --cov=app tests/ --cov-report=xml + - name: Upload coverage reports + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml From 92cb6b2269c733b4bd545162d02a9c9f2141b0e6 Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Tue, 3 Dec 2024 18:57:44 -0800 Subject: [PATCH 46/47] Updated pylint --- .github/workflows/pylint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 7041e703..88d01bb4 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -23,6 +23,8 @@ jobs: run: | pylint ./app; # pylint $(git ls-files '*.py') - name: Run tests with coverage + env: + PYTHONPATH: ${{ github.workspace }} run: | pytest --cov=app tests/ --cov-report=xml - name: Upload coverage reports From 2d12d05e6069f19222041057adc92ce41bf192ac Mon Sep 17 00:00:00 2001 From: Xingjian-Li-117 Date: Tue, 3 Dec 2024 19:04:11 -0800 Subject: [PATCH 47/47] Updated report upload in pylint --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 88d01bb4..e7c6c2ec 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -26,7 +26,7 @@ jobs: env: PYTHONPATH: ${{ github.workspace }} run: | - pytest --cov=app tests/ --cov-report=xml + pytest --cov=app tests/ --cov-report=term-missing - name: Upload coverage reports uses: codecov/codecov-action@v3 with: