diff --git a/backend/app/core/logging.py b/backend/app/core/logging.py index e50132368..1c9124392 100644 --- a/backend/app/core/logging.py +++ b/backend/app/core/logging.py @@ -37,8 +37,10 @@ def emit(self, record): ) logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True) - - if settings.HUU_ENVIRONMENT.lower() in ["development", "qa", "local"]: + ''' + A logging file can replace logging to standard out by changing sys.stdout to filename.log + ''' + if settings.HUU_ENVIRONMENT.lower() in ["development", "local"]: logger.add( sys.stdout, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function} - {message}", diff --git a/backend/app/modules/deps.py b/backend/app/modules/deps.py index 8e2fe0355..9ef7f5c03 100644 --- a/backend/app/modules/deps.py +++ b/backend/app/modules/deps.py @@ -26,6 +26,7 @@ def get_form_1(): import json with open("form_data/form1.json", "r") as f: FORM_1 = json.load(f) + FORM_1['id'] = 'guest' return FORM_1 @@ -35,6 +36,7 @@ def get_form_2(): import json with open("form_data/form2.json", "r") as f: FORM_2 = json.load(f) + FORM_2['id'] = 'host' return FORM_2 ################################################################################ diff --git a/backend/app/modules/intake_profile/controller.py b/backend/app/modules/intake_profile/controller.py index cfe79cba3..0498828b1 100644 --- a/backend/app/modules/intake_profile/controller.py +++ b/backend/app/modules/intake_profile/controller.py @@ -6,58 +6,77 @@ router = APIRouter() -@router.put("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) -def update_intake_profile_responses(user_id, body, db_session: DbSessionDep): - pass - # TODO: Implement update intake profile responses - # with db_session.begin() as session: - # user_repo = UserRepository(session) - # forms_repo = FormsRepository(session) - # user = user_repo.get_user(token_info['Username']) - - # form = forms_repo.get_form(form_id) - # if not form: - # return f"Form with id {form_id} does not exist.", 404 - - # valid_field_ids = form.get_field_ids() - # for response in body: - # response["user_id"] = user.id - # if response["field_id"] not in valid_field_ids: - # return f"Form {form_id} does not contain field id {response['field_id']}", 400 - - # forms_repo.add_user_responses(user.id, body) - - # return {}, 204 - - -@router.get("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) -def get_intake_profile_responses(user_id, db_session: DbSessionDep): - pass - # TODO: Implement get Intake Profile responses - # with db_session.begin() as session: - # user_repo = UserRepository(session) - # forms_repo = FormsRepository(session) - - # form = forms_repo.get_form_json(form_id) - # if not form: - # return f"Form with id {form_id} does not exist.", 404 - - # user = user_repo.get_user(token_info['Username']) - # responses = forms_repo.get_user_responses(user.id, form_id) - # if responses: - # return responses, 200 - # return [], 202 - - -@router.get("/form/{profile_id}", status_code=status.HTTP_200_OK) -def get_intake_profile_form(profile_id: int, - profile_form_1: Annotated[str, Depends(get_form_1)], - profile_form_2: Annotated[str, Depends(get_form_2)]): - """Get the Intake Profile form definition.""" - if profile_id == 1: - return profile_form_1 - if profile_id == 2: - return profile_form_2 +# @router.put("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) +# def update_intake_profile_responses(user_id, body, db_session: DbSessionDep): +# pass + # TODO: Implement the functionality to update intake profile responses. + # This should: + # 1. Use the `db_session` to initiate a transaction. + # 2. Retrieve the user data using the `user_id`. + # 3. Get the specific form by `form_id`. + # 4. Check if the form exists. If not, return an error (404). + # 5. Validate the field IDs in the response body against the form's field IDs. + # 6. If any field ID is invalid, return an error (400). + # 7. Save the valid responses in the database. + # 8. Return a success response with status 204 (no content) if everything goes well. + +# @router.get("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) +# def get_intake_profile_responses(user_id, db_session: DbSessionDep): +# pass + # TODO: Implement the functionality to retrieve intake profile responses for a user. + # This should: + # 1. Use the `db_session` to begin a transaction. + # 2. Retrieve the user using the `user_id`. + # 3. Fetch the form associated with the user's responses. + # 4. If the form does not exist, return a 404 error. + # 5. Get the user responses for the form from the database. + # 6. If responses are found, return them with status 200. + # 7. If no responses are found, return an empty list with status 202. + +# @router.get("/responses/{profile_type}", status_code=status.HTTP_200_OK) +# def get_intake_profile_form(profile_type: str, +# profile_form_1: Annotated[dict, Depends(get_form_1)], +# profile_form_2: Annotated[dict, Depends(get_form_2)]): +# """Get the Intake Profile form definition for host or guest.""" +# if profile_type == "guest": +# return profile_form_1 +# if profile_type == "host": +# return profile_form_2 + # TODO: Return the appropriate form based on the `profile_type` (either "guest" or "host"). + # 1. Use `profile_form_1` for the "guest" type and return it. + # 2. Use `profile_form_2` for the "host" type and return it. + # 3. If the `profile_type` is not recognized, raise a 404 error. + +# @router.get("/form/{profile_id}", status_code=status.HTTP_200_OK) +# def get_intake_profile_form(profile_id: int, +# profile_form_1: Annotated[str, Depends(get_form_1)], +# profile_form_2: Annotated[str, Depends(get_form_2)]): +# """Get the Intake Profile form definition.""" +# if profile_id == 1: +# return profile_form_1 +# if profile_id == 2: +# return profile_form_2 + # TODO: Return the form based on the `profile_id`. + # 1. If the `profile_id` is 1, return `profile_form_1`. + # 2. If the `profile_id` is 2, return `profile_form_2`. + # 3. If the `profile_id` is not valid, raise a 404 error. + +# This is your current working function: +@router.get("/form/{profile_type}", status_code=status.HTTP_200_OK) +def get_intake_profile_form(profile_type: str, + profile_form_1: Annotated[dict, Depends(get_form_1)], + profile_form_2: Annotated[dict, Depends(get_form_2)]): + """Get the Intake Profile form definition and responses for host or guest.""" + if profile_type == "guest": + return { + "form": profile_form_1, + "responses": profile_form_1.get("responses", []) + } + if profile_type == "host": + return { + "form": profile_form_2, + "responses": profile_form_2.get("responses", []) + } raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, - detail=f"Form with id {profile_id} does not exist.") + detail=f"Form type {profile_type} does not exist.") diff --git a/backend/form_data/form1.json b/backend/form_data/form1.json index db8ced8b7..8d79b7dbc 100644 --- a/backend/form_data/form1.json +++ b/backend/form_data/form1.json @@ -1 +1,344 @@ -{"id":"1","fieldGroups":[{"id":"1293","title":"Basic Information","fields":[{"id":"3922","title":"First Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"5572","title":"Last Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"2188","title":"Date of Birth","type":"date","properties":{},"validations":{"required":true}},{"id":"3924","title":"Gender Identity","type":"dropdown","properties":{"choices":[{"id":"1359","label":"Woman"},{"id":"5342","label":"Man"},{"id":"3151","label":"Questioning"},{"id":"2178","label":"Transgender"},{"id":"3667","label":"Non-binary"},{"id":"3708","label":"Doesn’t know or prefers not to answer\\t"}]},"validations":{"required":true}}]},{"id":"1348","title":"Contact Information","fields":[{"id":"7813","title":"Email","type":"email","properties":{},"validations":{"required":true}},{"id":"2044","title":"Phone Number","type":"number","properties":{},"validations":{"required":true}},{"id":"7504","title":"What is the best way to contact you?","type":"contact_method","properties":{},"validations":{"required":true},"linkedFields":{"emailFieldId":"3158","phoneFieldId":"9365"}}]},{"id":"6577","title":"Other Guests/Pets","fields":[{"id":"7709","title":"Additional Guests","type":"additional_guests","properties":{},"validations":{}},{"id":"7740","title":"Do you have any pets in your care?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"5056","title":"Pet Type","type":"pets","properties":{},"validations":{"required":false}}]},{"id":"1676","title":"Employment Information","fields":[{"id":"5478","title":"Are you currently employed?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"8533","title":"If yes, please describe your employment.","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5478","value":"yes"}}},{"id":"8487","title":"If no, are you currently looking for work? If so, what type?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5478","value":"no"}}}]},{"id":"5996","title":"Education","fields":[{"id":"6478","title":"Are you enrolled in an Educational Program?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7222","title":"If yes, please describe the program.","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"6478","value":"yes"}}},{"id":"7661","title":"If no, are you hoping to enroll in an Educational Program? If so, what type?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"6478","value":"no"}}}]},{"id":"4480","title":"Language Proficiency","fields":[{"id":"5479","title":"Are you bilingual or multilingual?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"2359","title":"If yes, what languages do you speak?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5479","value":"yes"}}}]},{"id":"5282","title":"Substance Use","fields":[{"id":"8229","title":"Do you smoke cigarettes?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7844","title":"Do you drink alcohol?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7494","title":"Do you use any other substances?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"3691","title":"Mental Health","fields":[{"id":"3831","title":"Do you suffer mental illness?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"7816","title":"Interest in Being a Guest","fields":[{"id":"1885","title":"Please share how you think participating in the Host Homes Program will help you obtain long-term housing and meet your educational and/or employment goals:","type":"long_text","properties":{},"validations":{"required":true}},{"id":"1185","title":"What kind of relationship do you hope to have with your host home?","type":"long_text","properties":{},"validations":{"required":true}},{"id":"3749","title":"Please describe any challenges you foresee encountering in participating in the Host Homes Program.","type":"long_text","properties":{},"validations":{"required":true}}]},{"id":"8837","title":"About You","fields":[{"id":"3411","title":"Please take some time to write an introduction of yourself that you would feel comfortable with the Host Homes Coordinator sharing with a potential host. Feel free to talk about your interests, your story or anything else that you think would be important to share:","type":"long_text","properties":{},"validations":{"required":true}}]}]} +{ + "id": "1", + "fieldGroups": [ + { + "id": "1293", + "title": "Basic Information", + "fields": [ + { + "id": "3922", + "title": "First Name", + "type": "short_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "5572", + "title": "Last Name", + "type": "short_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "2188", + "title": "Date of Birth", + "type": "date", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "3924", + "title": "Gender Identity", + "type": "dropdown", + "properties": { + "choices": [ + { + "id": "1359", + "label": "Woman" + }, + { + "id": "5342", + "label": "Man" + }, + { + "id": "3151", + "label": "Questioning" + }, + { + "id": "2178", + "label": "Transgender" + }, + { + "id": "3667", + "label": "Non-binary" + }, + { + "id": "3708", + "label": "Doesn’t know or prefers not to answer" + } + ] + }, + "validations": { + "required": true + } + } + ] + }, + { + "id": "1348", + "title": "Contact Information", + "fields": [ + { + "id": "7813", + "title": "Email", + "type": "email", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "2044", + "title": "Phone Number", + "type": "number", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7504", + "title": "What is the best way to contact you?", + "type": "contact_method", + "properties": {}, + "validations": { + "required": true + }, + "linkedFields": { + "emailFieldId": "7813", + "phoneFieldId": "2044" + } + } + ] + }, + { + "id": "6577", + "title": "Other Guests/Pets", + "fields": [ + { + "id": "7709", + "title": "Additional Guests", + "type": "additional_guests", + "properties": {}, + "validations": {} + }, + { + "id": "7740", + "title": "Do you have any pets in your care?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "5056", + "title": "Pet Type", + "type": "pets", + "properties": {}, + "validations": { + "required": false + } + } + ] + }, + { + "id": "1676", + "title": "Employment Information", + "fields": [ + { + "id": "5478", + "title": "Are you currently employed?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "8533", + "title": "If yes, please describe your employment.", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "5478", + "value": "yes" + } + } + }, + { + "id": "8487", + "title": "If no, are you currently looking for work? If so, what type?", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "5478", + "value": "no" + } + } + } + ] + }, + { + "id": "5996", + "title": "Education", + "fields": [ + { + "id": "6478", + "title": "Are you enrolled in an Educational Program?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7222", + "title": "If yes, please describe the program.", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "6478", + "value": "yes" + } + } + }, + { + "id": "7661", + "title": "If no, are you hoping to enroll in an Educational Program? If so, what type?", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "6478", + "value": "no" + } + } + } + ] + }, + { + "id": "4480", + "title": "Language Proficiency", + "fields": [ + { + "id": "5479", + "title": "Are you bilingual or multilingual?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "2359", + "title": "If yes, what languages do you speak?", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "5479", + "value": "yes" + } + } + } + ] + }, + { + "id": "5282", + "title": "Substance Use", + "fields": [ + { + "id": "8229", + "title": "Do you smoke cigarettes?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7844", + "title": "Do you drink alcohol?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7494", + "title": "Do you use any other substances?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + } + ] + }, + { + "id": "3691", + "title": "Mental Health", + "fields": [ + { + "id": "3831", + "title": "Do you suffer mental illness?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + } + ] + }, + { + "id": "7816", + "title": "Interest in Being a Guest", + "fields": [ + { + "id": "1885", + "title": "Please share how you think participating in the Host Homes Program will help you obtain long-term housing and meet your educational and/or employment goals:", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "1185", + "title": "What kind of relationship do you hope to have with your host home?", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "3749", + "title": "Please describe any challenges you foresee encountering in participating in the Host Homes Program.", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + } + ] + }, + { + "id": "8837", + "title": "About You", + "fields": [ + { + "id": "3411", + "title": "Please take some time to write an introduction of yourself that you would feel comfortable with the Host Homes Coordinator sharing with a potential host. Feel free to talk about your interests, your story or anything else that you think would be important to share:", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + } + ] + } + ] + } + \ No newline at end of file diff --git a/backend/poetry.lock b/backend/poetry.lock index 799f1cbac..4f08b462f 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "alembic" @@ -6,6 +6,7 @@ version = "1.13.2" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, @@ -17,7 +18,7 @@ SQLAlchemy = ">=1.3.0" typing-extensions = ">=4" [package.extras] -tz = ["backports.zoneinfo"] +tz = ["backports.zoneinfo ; python_version < \"3.9\""] [[package]] name = "annotated-types" @@ -25,6 +26,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -36,6 +38,7 @@ version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, @@ -47,7 +50,7 @@ sniffio = ">=1.1" [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.23)"] [[package]] @@ -56,6 +59,7 @@ version = "1.35.19" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "boto3-1.35.19-py3-none-any.whl", hash = "sha256:84b3fe1727945bc3cada832d969ddb3dc0d08fce1677064ca8bdc13a89c1a143"}, {file = "boto3-1.35.19.tar.gz", hash = "sha256:9979fe674780a0b7100eae9156d74ee374cd1638a9f61c77277e3ce712f3e496"}, @@ -75,6 +79,7 @@ version = "1.35.19" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "botocore-1.35.19-py3-none-any.whl", hash = "sha256:c83f7f0cacfe7c19b109b363ebfa8736e570d24922f16ed371681f58ebab44a9"}, {file = "botocore-1.35.19.tar.gz", hash = "sha256:42d6d8db7250cbd7899f786f9861e02cab17dc238f64d6acb976098ed9809625"}, @@ -94,6 +99,7 @@ version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, @@ -105,6 +111,7 @@ version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -116,6 +123,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "test"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -195,6 +204,7 @@ version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, @@ -206,6 +216,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["test"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -305,6 +316,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -319,10 +331,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "coverage" @@ -330,6 +344,7 @@ version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, @@ -406,7 +421,7 @@ files = [ ] [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -414,6 +429,7 @@ version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, @@ -463,6 +479,7 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["test"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -474,6 +491,7 @@ version = "2.6.1" description = "DNS toolkit" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -494,6 +512,7 @@ version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, @@ -509,6 +528,7 @@ version = "0.113.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastapi-0.113.0-py3-none-any.whl", hash = "sha256:c8d364485b6361fb643d53920a18d58a696e189abcb901ec03b487e35774c476"}, {file = "fastapi-0.113.0.tar.gz", hash = "sha256:b7cf9684dc154dfc93f8b718e5850577b529889096518df44defa41e73caf50f"}, @@ -535,6 +555,7 @@ version = "0.0.5" description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastapi_cli-0.0.5-py3-none-any.whl", hash = "sha256:e94d847524648c748a5350673546bbf9bcaeb086b33c24f2e82e021436866a46"}, {file = "fastapi_cli-0.0.5.tar.gz", hash = "sha256:d30e1239c6f46fcb95e606f02cdda59a1e2fa778a54b64686b3ff27f6211ff9f"}, @@ -553,6 +574,7 @@ version = "3.16.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, @@ -561,7 +583,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "greenlet" @@ -569,6 +591,8 @@ version = "3.1.0" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main", "test"] +markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, @@ -648,6 +672,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -659,6 +684,7 @@ version = "1.0.5" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, @@ -680,6 +706,7 @@ version = "0.6.1" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, @@ -728,6 +755,7 @@ version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -741,7 +769,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -753,6 +781,7 @@ version = "3.9" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "idna-3.9-py3-none-any.whl", hash = "sha256:69297d5da0cc9281c77efffb4e730254dd45943f45bbfb461de5991713989b1e"}, {file = "idna-3.9.tar.gz", hash = "sha256:e5c5dafde284f26e9e0f28f6ea2d6400abd5ca099864a67f576f3981c6476124"}, @@ -767,6 +796,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -774,13 +804,14 @@ files = [ [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -795,6 +826,7 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -806,6 +838,7 @@ version = "1.0.0" description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "joserfc-1.0.0-py3-none-any.whl", hash = "sha256:1de2c3ac203db8fceb2e84c1e78ba357030b195c21af046a1411711927654a09"}, {file = "joserfc-1.0.0.tar.gz", hash = "sha256:298a9820c76576f8ca63375d1851cc092f3f225508305c7a36c4632cec38f7bc"}, @@ -823,6 +856,7 @@ version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false python-versions = "<4.0,>=3.5" +groups = ["main"] files = [ {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, @@ -833,7 +867,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] +dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] [[package]] name = "mako" @@ -841,6 +875,7 @@ version = "1.3.5" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, @@ -860,6 +895,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -884,6 +920,7 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -953,6 +990,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -964,6 +1002,7 @@ version = "5.0.14" description = "" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "moto-5.0.14-py2.py3-none-any.whl", hash = "sha256:c738ffe85d3844ef37b865951736c4faf2e0f3e4f05db87bdad97a6c01b88174"}, {file = "moto-5.0.14.tar.gz", hash = "sha256:0f849243269fd03372426c302b18cb605302da32620d7f0266be6a40735b2acd"}, @@ -1010,6 +1049,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -1021,6 +1061,7 @@ version = "4.3.3" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, @@ -1037,6 +1078,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1052,6 +1094,7 @@ version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, @@ -1133,6 +1176,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "test"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1144,6 +1189,7 @@ version = "2.9.1" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"}, {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"}, @@ -1159,7 +1205,7 @@ typing-extensions = [ [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] [[package]] name = "pydantic-core" @@ -1167,6 +1213,7 @@ version = "2.23.3" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, @@ -1268,6 +1315,7 @@ version = "2.5.2" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, @@ -1288,6 +1336,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -1302,6 +1351,7 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -1322,6 +1372,7 @@ version = "1.7.1" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pyproject_api-1.7.1-py3-none-any.whl", hash = "sha256:2dc1654062c2b27733d8fd4cdda672b22fe8741ef1dde8e3a998a9547b071eeb"}, {file = "pyproject_api-1.7.1.tar.gz", hash = "sha256:7ebc6cd10710f89f4cf2a2731710a98abce37ebff19427116ff2174c9236a827"}, @@ -1340,6 +1391,7 @@ version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, @@ -1360,6 +1412,7 @@ version = "0.11.1" description = "A pytest plugin for verifying alembic migrations." optional = false python-versions = "<4,>=3.6" +groups = ["test"] files = [ {file = "pytest_alembic-0.11.1-py3-none-any.whl", hash = "sha256:f83e8c1534d50ced053aa4b1dbf6e261f4674aa626cb852fc1dcb565049ae152"}, {file = "pytest_alembic-0.11.1.tar.gz", hash = "sha256:a920d8770b5be77326c5c1b2bd8d4d4a0dd8fc2c2d57abbcd1fec28a21131b85"}, @@ -1376,6 +1429,7 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -1394,6 +1448,7 @@ version = "3.15.0" description = "Pytest plugin to randomly order tests and control random.seed." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"}, {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"}, @@ -1408,6 +1463,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1422,6 +1478,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1436,6 +1493,7 @@ version = "0.0.18" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, @@ -1447,6 +1505,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1509,6 +1568,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1530,6 +1590,7 @@ version = "0.25.3" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb"}, {file = "responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba"}, @@ -1541,7 +1602,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "rich" @@ -1549,6 +1610,7 @@ version = "13.8.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, @@ -1567,6 +1629,7 @@ version = "0.10.2" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69"}, {file = "s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6"}, @@ -1584,6 +1647,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -1595,6 +1659,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main", "test"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1606,6 +1671,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1617,6 +1683,7 @@ version = "2.0.34" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db"}, {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b"}, @@ -1704,6 +1771,7 @@ version = "0.38.5" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "starlette-0.38.5-py3-none-any.whl", hash = "sha256:632f420a9d13e3ee2a6f18f437b0a9f1faecb0bc42e1942aa2ea0e379a4c4206"}, {file = "starlette-0.38.5.tar.gz", hash = "sha256:04a92830a9b6eb1442c766199d62260c3d4dc9c4f9188360626b1e0273cb7077"}, @@ -1721,6 +1789,7 @@ version = "4.18.1" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "tox-4.18.1-py3-none-any.whl", hash = "sha256:35d472032ee1f73fe20c3e0e73d7073a4e85075c86ff02c576f9fc7c6a15a578"}, {file = "tox-4.18.1.tar.gz", hash = "sha256:3c0c96bc3a568a5c7e66387a4cfcf8c875b52e09f4d47c9f7a277ec82f1a0b11"}, @@ -1739,7 +1808,7 @@ virtualenv = ">=20.26.3" [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-argparse-cli (>=1.17)", "sphinx-autodoc-typehints (>=2.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=24.8)"] -testing = ["build[virtualenv] (>=1.2.2)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=74.1.2)", "time-machine (>=2.15)", "wheel (>=0.44)"] +testing = ["build[virtualenv] (>=1.2.2)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=74.1.2)", "time-machine (>=2.15) ; implementation_name != \"pypy\"", "wheel (>=0.44)"] [[package]] name = "typer" @@ -1747,6 +1816,7 @@ version = "0.12.5" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"}, {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, @@ -1764,6 +1834,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1775,13 +1846,14 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1792,6 +1864,7 @@ version = "0.30.6" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, @@ -1804,12 +1877,12 @@ h11 = ">=0.8" httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -1817,6 +1890,8 @@ version = "0.20.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" files = [ {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996"}, {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b"}, @@ -1853,7 +1928,7 @@ files = [ [package.extras] docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0) ; python_version >= \"3.12\"", "aiohttp (>=3.8.1) ; python_version < \"3.12\"", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] [[package]] name = "virtualenv" @@ -1861,6 +1936,7 @@ version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, @@ -1873,7 +1949,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchfiles" @@ -1881,6 +1957,7 @@ version = "0.24.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"}, {file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"}, @@ -1976,6 +2053,7 @@ version = "13.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, @@ -2071,6 +2149,7 @@ version = "3.0.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, @@ -2088,13 +2167,15 @@ version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, ] [package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] +dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "xmltodict" @@ -2102,12 +2183,13 @@ version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" optional = false python-versions = ">=3.4" +groups = ["test"] files = [ {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" content-hash = "3a16491aac1a5953b2ed3568e05911f5d9eb149d972b757a20308ad2d42c2451" diff --git a/docs/Guest_Sequence.drawio b/docs/Guest_Sequence.drawio new file mode 100644 index 000000000..33e4b4207 --- /dev/null +++ b/docs/Guest_Sequence.drawio @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Guest_Sequence.svg b/docs/Guest_Sequence.svg new file mode 100644 index 000000000..a9a8cba33 --- /dev/null +++ b/docs/Guest_Sequence.svg @@ -0,0 +1,4 @@ + + + +
Coordinator
UI
API
Cognito
Lambda
Guest
DB
S3
Invite Guest by email1Submit invitation request2Initiate user invite3Trigger email invitation4Send invitation email5Follow link and provide preferred name/email6Submit account creation request7Validate and create user record in Guests table8Confirm successful user creation9Reroute to Guest Intake Profile10Complete Intake Profile questionnaire11Validate and submit Intake Profile12Validate and create/update rows foruser in Guest Intake Responses table13Upload photos to Guest Photos bucket14
\ No newline at end of file diff --git a/docs/HUU_Incubator_Architecture.drawio b/docs/HUU_Incubator_Architecture.drawio new file mode 100644 index 000000000..098d2bd0a --- /dev/null +++ b/docs/HUU_Incubator_Architecture.drawio @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/HUU_Incubator_Architecture.svg b/docs/HUU_Incubator_Architecture.svg new file mode 100644 index 000000000..982c58e7a --- /dev/null +++ b/docs/HUU_Incubator_Architecture.svg @@ -0,0 +1,4 @@ + + + +
S3
Lambda
ECR
read/write database records
ECS
has DNS A record
for public IP
Route 53
triggers for
invite emails
and OIDC callbacks
Cognito
pull API
container 
image
read/write files
RDS
manage users
Secrets Manager
read secrets
ELB
Browser
navigates to
*.homeunite.us
User
sends HTTPS
requests
\ No newline at end of file diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..88697c69c --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,36 @@ +# Architecture Diagrams + +### Infrastructure + +##### Incubator + +![HomeUniteUs deployment architecture](HUU_Incubator_Architecture.svg) + +##### High-level + +```mermaid +architecture-beta + group incubator(cloud)[Incubator AWS Account] + group browser(internet)[Browser] + + service db(database)[PostgreSQL DB] in incubator + service s3(disk)[S3] in incubator + service cognito(database)[Cognito] in incubator + service secrets(database)[Secrets Manager] in incubator + service api(server)[ECS] in incubator + service app(server)[React App] in browser + + junction junctionCenter + junction junctionRight + + app:R --> L:api + api:R -- L:junctionCenter + junctionCenter:R -- L:junctionRight + junctionCenter:T --> B:db + junctionCenter:B --> T:s3 + junctionRight:T --> B:cognito + junctionRight:B --> T:secrets + +``` + + diff --git a/docs/sequence.md b/docs/sequence.md new file mode 100644 index 000000000..ef4566b57 --- /dev/null +++ b/docs/sequence.md @@ -0,0 +1,42 @@ +# Sequence Diagrams + +### Guest + +```mermaid +sequenceDiagram + autonumber + Coordinator->>UI: Invite Guest by email + UI->>API: Submit invitation request + API->>Cognito: Initiate user invite + Cognito->>Lambda: Trigger email invitation + Lambda->>Guest: Send invitation email + Guest->>UI: Follow link and provide preferred name/email + UI->>API: Submit account creation request + API->>DB: Validate and create user record in Guests table + API->>UI: Confirm successful user creation + UI->>Guest: Reroute to Guest Intake Profile + Guest->>UI: Complete Intake Profile questionnaire + UI->>API: Validate and submit Intake Profile + API->>DB: Validate and create/update rows for
user in Guest Intake Responses table + API->>S3: Upload photos to Guest Photos bucket +``` + +### Host + +```mermaid +sequenceDiagram + autonumber + Host->>UI: Click "Sign Up" and provide preferred name/email + UI->>API: Submit account creation request + API->>DB: Validate and create user record in Hosts table + API->>UI: Confirm successful user creation + UI->>Host: Reroute to Host Intake Profile + Host->>UI: Complete Intake Profile questionnaire + UI->>API: Validate and submit Intake Profile + API->>DB: Validate and create/update rows for
user in Host Intake Responses table + API->>S3: Upload photos to Host Photos bucket +``` + +##### As SVG + +![HomeUniteUs Guest sequence diagram](Guest_Sequence.svg) \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 65269a2a1..cf265286c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -55,7 +55,7 @@ "semver": "^7.6.0", "ts-node": "^10.9.1", "typescript": "^4.7.4", - "vite": "^5.4.8", + "vite": "^5.4.17", "vitest": "^2.1.2" }, "engines": { @@ -398,9 +398,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", - "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -456,13 +456,12 @@ } }, "node_modules/@bundled-es-modules/cookie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", - "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", "dev": true, - "license": "ISC", "dependencies": { - "cookie": "^0.5.0" + "cookie": "^0.7.2" } }, "node_modules/@bundled-es-modules/statuses": { @@ -4331,11 +4330,10 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11412,10 +11410,11 @@ } }, "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "version": "5.4.17", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz", + "integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/frontend/package.json b/frontend/package.json index 044dda784..dc7b21f4d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -80,7 +80,7 @@ "semver": "^7.6.0", "ts-node": "^10.9.1", "typescript": "^4.7.4", - "vite": "^5.4.8", + "vite": "^5.4.17", "vitest": "^2.1.2" } } diff --git a/frontend/src/features/authentication/SignInForm.tsx b/frontend/src/features/authentication/SignInForm.tsx index 0d968a245..30d8d08e4 100644 --- a/frontend/src/features/authentication/SignInForm.tsx +++ b/frontend/src/features/authentication/SignInForm.tsx @@ -26,9 +26,9 @@ const validationSchema = object({ }); export const SignInForm = ({ - onSubmit, + onSubmit, isLoading, - getTokenIsLoading + getTokenIsLoading, }: SignInFormProps) => { const { handleSubmit, diff --git a/frontend/src/features/authentication/SignUpForm.tsx b/frontend/src/features/authentication/SignUpForm.tsx index cf435e5a1..3e8a18642 100644 --- a/frontend/src/features/authentication/SignUpForm.tsx +++ b/frontend/src/features/authentication/SignUpForm.tsx @@ -34,7 +34,7 @@ export const SignUpForm = ({ type, getTokenIsLoading = false, signUpHostIsLoading = false, - signUpCoordinatorIsLoading = false + signUpCoordinatorIsLoading = false, }: SignUpFormProps) => { const isAnyLoading = isLoading || diff --git a/frontend/src/features/intake-profile/IntakeProfileGroups.tsx b/frontend/src/features/intake-profile/IntakeProfileGroups.tsx index 698385aef..9f10a71f4 100644 --- a/frontend/src/features/intake-profile/IntakeProfileGroups.tsx +++ b/frontend/src/features/intake-profile/IntakeProfileGroups.tsx @@ -49,3 +49,217 @@ export const FieldGroupList = () => { ); }; + +interface RenderFieldProps { + groupId: string; + field: Fields; +} + +export const RenderFields = ({groupId, field}: RenderFieldProps) => { + const {errors, handleChange, setFieldValue, values} = + useFormikContext(); + const groupValues = values[groupId]; + + const props = { + name: `${groupId}.${field.id}`, + value: groupValues[field.id], + onChange: handleChange, + }; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const error = Boolean(errors[groupId]?.[field.id]); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const helperText = errors[groupId]?.[field.id]; + + switch (field.type) { + case 'additional_guests': + return ( + + ); + case 'date': + return ( + { + setFieldValue(name, value); + }} + /> + ); + case 'dropdown': + if (field.properties.choices === undefined) + throw new Error('Invalid field type'); + + return ( + + + Select a choice + + + {helperText} + + ); + case 'email': + return ( + + ); + case 'long_text': + return ( + + ); + case 'number': + return ( + + ); + case 'pets': + return ( + + ); + case 'short_text': + return ( + + ); + case 'contact_method': + // eslint-disable-next-line no-case-declarations + const {emailFieldId, phoneFieldId} = field.linkedFields || {}; + + // eslint-disable-next-line no-case-declarations + let isEmailFilled = false; + // eslint-disable-next-line no-case-declarations + let isPhoneFilled = false; + if (emailFieldId) { + const emailValue = values[groupId][emailFieldId]; + isEmailFilled = + typeof emailValue === 'string' && /\S+@\S+\.\S+/.test(emailValue); + } + // eslint-disable-next-line no-case-declarations + if (phoneFieldId) { + const phoneValue = values[groupId][phoneFieldId]; + isPhoneFilled = + typeof phoneValue === 'string' && phoneRegExp.test(phoneValue); + } + + return ( + + {field.title} + + } + label="Phone" + disabled={!isPhoneFilled} + /> + } + label="Email" + disabled={!isEmailFilled} + /> + + {helperText} + + ); + + case 'yes_no': + return ( + + {field.title} + + } label="Yes" /> + } label="No" /> + + {helperText} + + ); + default: + throw new Error('Invalid field type'); + } +}; diff --git a/frontend/src/features/intake-profile/helpers/createInitialValues.ts b/frontend/src/features/intake-profile/helpers/createInitialValues.ts index f15c55997..01589cb04 100644 --- a/frontend/src/features/intake-profile/helpers/createInitialValues.ts +++ b/frontend/src/features/intake-profile/helpers/createInitialValues.ts @@ -2,7 +2,7 @@ import {InitialValues} from '../../../pages/intake-profile/IntakeProfile'; import {FieldGroup, FieldTypes, Response} from '../../../services/profile'; /** - * Creates an object used for the initial Formik valiues + * Creates an object used for the initial Formik values * It takes the form of: * { * fieldGroupId: { @@ -26,6 +26,10 @@ const fieldDefaultValue = (fieldType: FieldTypes) => { return ''; case 'yes_no': return ''; + case 'multiple_choice': + return []; + case 'contact_method': + return ''; case 'pets': return []; default: @@ -34,16 +38,29 @@ const fieldDefaultValue = (fieldType: FieldTypes) => { }; export const createInitialValues = ( - fieldGroups: FieldGroup[], - responses: Response[], + fieldGroups: FieldGroup[] | undefined, + responses: Response[] | undefined, ): InitialValues => { + // Early return if fieldGroups is undefined or empty + if (!fieldGroups || fieldGroups.length === 0) { + return {}; + } + + // Use empty array if responses is undefined + const safeResponses = responses || []; + return fieldGroups.reduce((acc: InitialValues, fieldGroup) => { - const fields = fieldGroup.fields.reduce((acc, field) => { + // Skip groups without fields + if (!fieldGroup.fields || fieldGroup.fields.length === 0) { + return acc; + } + + const fields = fieldGroup.fields.reduce((fieldAcc, field) => { return { - ...acc, + ...fieldAcc, [field.id]: - responses.find(response => response.fieldId === field.id)?.value || - fieldDefaultValue(field.type), + safeResponses.find(response => response.fieldId === field.id) + ?.value || fieldDefaultValue(field.type), }; }, {}); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 6f0eb0882..10f77c50d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -46,7 +46,7 @@ import { IntakeProfileSection, } from './pages'; import {SystemAdminDashboard} from './pages/SystemAdminDashboard'; -import {enableMocking} from './utils/testing/browser'; +// import {enableMocking} from './utils/testing/browser'; import {useAppDispatch} from './redux/hooks/store'; import {setCredentials} from './redux/authSlice'; import NotFound from './pages/NotFound'; @@ -170,21 +170,21 @@ function HuuApp() { const appRoot = document.getElementById('root') as HTMLElement; -enableMocking().then(() => { - ReactDOM.createRoot(appRoot).render( - - - - - - - - - - - - - - , - ); -}); +// enableMocking().then(() => { +ReactDOM.createRoot(appRoot).render( + + + + + + + + + + + + + + , +); +// }); diff --git a/frontend/src/pages/authentication/SignUp.tsx b/frontend/src/pages/authentication/SignUp.tsx index 260825d3b..ad58b8c20 100644 --- a/frontend/src/pages/authentication/SignUp.tsx +++ b/frontend/src/pages/authentication/SignUp.tsx @@ -18,25 +18,33 @@ import { import {isErrorWithMessage, isFetchBaseQueryError} from '../../redux/helpers'; import {useAuthenticateWithOAuth} from '../../features/authentication/hooks/useAuthenticateWithOAuth'; import {FormContainer, SignUpForm} from '../../features/authentication'; -import { LocationState } from './SignIn'; +// Uncomment this import if it's in a separate file +// import { LocationState } from './SignIn'; + +// You might need to define LocationState if it's not imported +interface LocationState { + from?: { + pathname: string; + }; +} export const SignUp = () => { const [errorMessage, setErrorMessage] = React.useState(''); const {type} = useParams(); const navigate = useNavigate(); + const location = useLocation(); + + // Get location state information + const locationState = location.state as LocationState; + + // Save location from which user was redirected to login page + const from = locationState?.from?.pathname || '/'; const [signUp, {isLoading: signUpIsLoading}] = useSignUpMutation(); const [googleSignUp, {isLoading: getTokenIsLoading}] = useGoogleSignUpMutation(); - // const location = useLocation() - // // get type from params - // const locationState = location.state as LocationState; - - // // Save location from which user was redirected to login page - // const from = locationState?.from?.pathname || '/'; - const callbackUri = `/signup/${type}`; useAuthenticateWithOAuth({ query: googleSignUp, diff --git a/frontend/src/pages/guest-dashboard/GuestDashboard.tsx b/frontend/src/pages/guest-dashboard/GuestDashboard.tsx index f7ae20d50..275a981b1 100644 --- a/frontend/src/pages/guest-dashboard/GuestDashboard.tsx +++ b/frontend/src/pages/guest-dashboard/GuestDashboard.tsx @@ -36,7 +36,8 @@ const tasks: Task[] = [ description: 'Start your guest application to move on to the next step.', linkText: 'Start Application', - url: 'profile/1', + // url: 'profile/1/group/1', + url: 'profile/guest', }, { id: 2, diff --git a/frontend/src/pages/intake-profile/IntakeProfile.tsx b/frontend/src/pages/intake-profile/IntakeProfile.tsx index e981abe8d..f91532861 100644 --- a/frontend/src/pages/intake-profile/IntakeProfile.tsx +++ b/frontend/src/pages/intake-profile/IntakeProfile.tsx @@ -1,6 +1,19 @@ +import React, {useState, useEffect} from 'react'; +import { + Button, + Stack, + useMediaQuery, + useTheme, + CircularProgress, + Typography, +} from '@mui/material'; import {Container, Stack, useMediaQuery, useTheme} from '@mui/material'; import {Outlet, useLocation, useNavigate, useParams} from 'react-router-dom'; import {Formik} from 'formik'; +import {buildValidationSchema, createInitialValues} from './helpers'; +import {useGetProfileQuery, Response} from '../../services/profile'; +import {ProfileSidebar} from '../../features/intake-profile'; + import {useState} from 'react'; import {createInitialValues} from '../../features/intake-profile/helpers'; import { @@ -18,14 +31,93 @@ export type InitialValues = Record; export const IntakeProfile = () => { const theme = useTheme(); const navigate = useNavigate(); + const toolbarHeight = Number(theme.mixins.toolbar.minHeight); + const params = useParams(); const {profileId, groupId} = useParams(); const location = useLocation(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [showSections, setShowSections] = useState(isMobile); const [selectedItem, setSelectedItem] = useState( - groupId || null, + params.groupId || null, ); + // Debug logging for all parameters + useEffect(() => { + console.log('FULL ROUTE PARAMETERS:', params); + console.log('CURRENT LOCATION:', location); + }, [params, location]); + + // Determine profile type with extensive fallback + const profileType = + params.profileType || params.profileId?.split('-')[0] || 'guest'; + + // const userId = + // params.userId || + // (params.profileId?.split('-')[1]) || + // 'anonymous'; + + // Fetch profile data + const { + data: profileData, + isLoading: isProfileLoading, + error: profileError, + } = useGetProfileQuery({ + profileId: profileType, + }); + + // Extensive error logging + useEffect(() => { + if (profileError) { + console.error('PROFILE FETCH ERROR:', profileError); + } + }, [profileError]); + + // Loading state + if (isProfileLoading) { + return ( + + + + ); + } + + // Error handling + if (profileError) { + return ( + + + Error loading profile. Please try again. + + {JSON.stringify(profileError)} + + ); + } + + // If no data after loading, return null or show error + if (!profileData) { + return ( + + No profile data found + + ); + } + + const {form, responses = []} = profileData; + const fieldGroups = form?.fieldGroups || []; + + // create validation schema for current group + const validationSchema = + params.groupId === undefined + ? {} + : buildValidationSchema(fieldGroups, params.groupId); const {data: profileData} = useGetProfileQuery( {profileId: profileId}, {skip: !profileId}, @@ -48,12 +140,15 @@ export const IntakeProfile = () => { // groupId === undefined ? {} : buildValidationSchema(fieldGroups, groupId); // create initial values from responses and fieldGroups + const initialValues = createInitialValues(fieldGroups, responses); + const initalValues = createInitialValues(fieldGroups, responses); const currentIndex = fieldGroups.findIndex( group => group.id === selectedItem, ); + // Navigation and section management functions function toggleShowSections() { setShowSections(!showSections); } @@ -63,7 +158,6 @@ export const IntakeProfile = () => { const nextGroupId = fieldGroups[currentIndex + 1].id; setSelectedItem(nextGroupId); navigate(`group/${nextGroupId}`); - //need to add autosave feature } } @@ -72,33 +166,37 @@ export const IntakeProfile = () => { const prevGroupId = fieldGroups[currentIndex - 1].id; setSelectedItem(prevGroupId); navigate(`group/${prevGroupId}`); - //need to add autosave feature } } const handleSubmitApplication = () => { - // submit the application after review + // TODO: Implement application submission logic + console.log('Submitting application'); }; return ( { - if (!groupId) { + if (!params.groupId) { console.error('groupId is not defined in on submit'); return; } - const updateResponses = Object.entries(values[groupId]).map( + const updateResponses = Object.entries(values[params.groupId]).map( ([fieldId, value]) => { const response = responses.find( response => response.fieldId === fieldId, ); if (response) { - response.value = value; - return response; + return { + ...response, + value, + }; } else { return { fieldId, @@ -122,12 +220,77 @@ export const IntakeProfile = () => { > + + + + + + + {location.pathname.includes('review') ? ( + + ) : ( + + )} + + { +// const id = req.params.profileId; +// const profile = intakeProfiles.find(p => p.id === id); + +// if (profile) { +// return HttpResponse.json(profile); +// } + +// return new HttpResponse(null, {status: 404}); +// }), + +// http.get( +// '/api/intake-profile/responses/:userId', +// () => { +// const fields = intakeProfiles[0].fieldGroups +// .map(fieldGroup => fieldGroup.fields) +// .flat(); + +// const responses = fields.map(field => { +// const value = getResponseValue(field); +// return { +// fieldId: field.id, +// value, +// }; +// }); + +// // return a list of filled in responses +// return HttpResponse.json({responses}); +// // return an empty list of responses +// // return HttpResponse.json({responses: []}); +// }, +// ), +// ]; + export const handlers = [ http.post('/api/profile/responses', async ({request}) => { const responses = await request.json();