From 4d3dac8838d0038f6dfc62a93f4d60c42a4a665b Mon Sep 17 00:00:00 2001 From: Shelby Potts Date: Thu, 24 Apr 2025 10:55:05 -0500 Subject: [PATCH 1/3] logging changes, conditional asset copy --- app/analytics/UserAnalytics.py | 15 ++++++++++++--- app/main.py | 3 --- app/pages/MatchPage.py | 8 ++++---- app/pages/UserPage.py | 28 +++++++++++++++------------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app/analytics/UserAnalytics.py b/app/analytics/UserAnalytics.py index e5156fe..b221de4 100644 --- a/app/analytics/UserAnalytics.py +++ b/app/analytics/UserAnalytics.py @@ -6,6 +6,9 @@ import json import os import shutil +import logging + +logging.basicConfig(level=logging.INFO) class UserAnalytics: def __init__(self): @@ -28,12 +31,12 @@ def __init__(self): user_data = json.load(file) self.user_data = user_data - - def get_media_file_paths(self): + # need to copy the files from the media_path to the assets_dir _copy_files(self.media_path, self.assets_path) - jpg_files = [f for f in os.listdir(self.assets_path) if f.endswith(".jpg")] + def get_media_file_paths(self): + jpg_files = [f for f in os.listdir(self.assets_path) if f.endswith(".jpg") or f.endswith(".jpeg") or f.endswith(".png")] return jpg_files def get_account_data(self): @@ -167,6 +170,12 @@ def collect_location_from_ip(self): def _copy_files(src_dir, dest_dir): os.makedirs(dest_dir, exist_ok=True) + logging.info(f"Copying images files from media directory: {src_dir} to asset directory; {dest_dir}." ) + + # only proceed if the destination directory is empty + if os.listdir(dest_dir): + logging.info(f"Asset directory: '{dest_dir}' is not empty. Skipping copy...") + return # loop through all files in source directory for file_name in os.listdir(src_dir): diff --git a/app/main.py b/app/main.py index bb8a6e5..6e820c3 100644 --- a/app/main.py +++ b/app/main.py @@ -15,8 +15,6 @@ import pages.HomePage as HomePage import pages.InfoPage as InfoPage -from tools.Logger import logger - external_stylesheets = [dmc.theme.DEFAULT_COLORS] server = Flask(__name__) app = Dash(__name__, server=server, use_pages=True, external_stylesheets=external_stylesheets) @@ -76,5 +74,4 @@ def get_additional_text(page_name): host = os.environ.get("HOST") port = int(os.environ.get("PORT", 8050)) - logger.info(f"Running the Hinge Data Analysis app on {host}:{port}...") app.run(debug=True, host=host, port=port) \ No newline at end of file diff --git a/app/pages/MatchPage.py b/app/pages/MatchPage.py index 1919533..1a1694e 100644 --- a/app/pages/MatchPage.py +++ b/app/pages/MatchPage.py @@ -54,13 +54,13 @@ def response_latency_hist(): dmc.Text("Response Latency between Match and First Message Sent", weight=700, size="xl"), dmc.Space(h=10), dmc.Text("This graph visualizes the response latency, or the time delay between when a match occurs and when the first message is sent." \ - "Shorter latencies may indicate higher levels of engagement or interest, while longer delays could suggest hesitation, lower enthusiasm, or forgotten matches.", size="md"), + " Shorter latencies may indicate higher levels of engagement or interest, while longer delays could suggest hesitation, lower enthusiasm, or forgotten matches.", size="md"), dmc.Space(h=10), dcc.Graph(figure=fig) ], shadow="sm", radius="md", - style={"height": "520px"}, + style={"height": "550px"}, ) def match_duration_hist(): @@ -77,14 +77,14 @@ def match_duration_hist(): dmc.Text("Duration of Time Between Match and Remove", weight=700, size="xl"), dmc.Space(h=10), dmc.Text("This histogram visualizes the duration of a connection and when it was removed or blocked." \ - "Short durations might reflect mismatched expectations, ghosting, or immediate disinterest, while longer " \ + " Short durations might reflect mismatched expectations, ghosting, or immediate disinterest, while longer " \ "durations may point to sustained conversations or lingering connections that eventually tapered off. ", size="md"), dmc.Space(h=10), dcc.Graph(figure=fig) ], shadow="sm", radius="md", - style={"height": "520px"}, + style={"height": "550px"}, ) def match_removal_count_scatter(): diff --git a/app/pages/UserPage.py b/app/pages/UserPage.py index 605c80a..599196a 100644 --- a/app/pages/UserPage.py +++ b/app/pages/UserPage.py @@ -9,8 +9,10 @@ BLUE = "#3BAAC4" REDISH = "#C4553B" +user_analytics = UserAnalytics() + def stringency_vs_flexibility(): - dealbreaker_counts = UserAnalytics().count_stringeny_attributes() + dealbreaker_counts = user_analytics.count_stringeny_attributes() category_labels = { "physical": "Age & Height", @@ -64,7 +66,7 @@ def stringency_vs_flexibility(): ) def geolocation(): - df = UserAnalytics().collect_location_from_ip() + df = user_analytics.collect_location_from_ip() fig = px.scatter_geo( df, lat="latitude", @@ -92,14 +94,14 @@ def geolocation(): withBorder=True, shadow="sm", radius="md", - style={"height": "520px"}, + style={"height": "550px"}, ) def potential_misalignments(): # define categories categories = ["Religion", "Ethnicity", "Smoking", "Drinking", "Marijuana", "Drugs", "Children", "Family Plans", "Education", "Politics"] - profile_selections, preferences_selections = UserAnalytics().profile_preference_selections() + profile_selections, preferences_selections = user_analytics.profile_preference_selections() # create table with two data columns fig = go.Figure(data=[go.Table( @@ -111,7 +113,7 @@ def potential_misalignments(): align="left") )]) - fig.update_layout(title="Profile Visibility Comparison Between User A and User B") + fig.update_layout(title="Profile Visibility Comparison Between The User and Their Preferences") return dmc.Card( children=[ @@ -130,7 +132,7 @@ def potential_misalignments(): def disclosure_vs_privacy(): - category_counts = UserAnalytics().count_displayed_attributes() + category_counts = user_analytics.count_displayed_attributes() category_labels = { "identity": "Identity & Demographics", @@ -183,7 +185,7 @@ def disclosure_vs_privacy(): ) def user_photo_slideshow(): - jpg_files = UserAnalytics().get_media_file_paths() + jpg_files = user_analytics.get_media_file_paths() return dmc.Card( children=[ @@ -196,7 +198,7 @@ def user_photo_slideshow(): withBorder=True, shadow="sm", radius="md", - style={"width": "500px", "height": "520px", "padding": "20px"}, + style={"width": "500px", "height": "550px", "padding": "20px"}, ) @callback( @@ -210,7 +212,7 @@ def update_image(n_intervals, jpg_files): def create_user_location_card(): - user_location = UserAnalytics().build_user_location_dict() + user_location = user_analytics.build_user_location_dict() fig = px.scatter_mapbox( lat=[user_location["latitude"]], @@ -239,12 +241,12 @@ def create_user_location_card(): withBorder=True, shadow="sm", radius="md", - style={"width": "500px", "height": "520px"}, + style={"width": "500px", "height": "550px"}, ) def create_user_summary_card(): - user_summary = UserAnalytics().build_user_summary_dict() + user_summary = user_analytics.build_user_summary_dict() return dmc.Card( children=[ @@ -268,7 +270,7 @@ def create_user_summary_card(): withBorder=True, shadow="sm", radius="md", - style={"width": "500px", "padding": "20px", "height": "520px"}, + style={"width": "500px", "padding": "20px", "height": "550px"}, ) layout = html.Div([ @@ -291,7 +293,7 @@ def create_user_summary_card(): span=4 ) ], - style={"height": "50vh"} ), + style={"height": "60vh"} ), dmc.Space(h=120), disclosure_vs_privacy(), potential_misalignments(), From 80de87fb1473ae21047bce457b8c08b5ad0d00a8 Mon Sep 17 00:00:00 2001 From: Shelby Potts Date: Fri, 25 Apr 2025 08:57:16 -0500 Subject: [PATCH 2/3] docker reading env file now, rewrote readme --- .env-defaults | 6 + .env_example | 6 - .gitignore | 2 +- BREAKING.md | 11 -- Dockerfile | 2 +- README.md | 228 ++++++++++++++----------- app/analytics/MatchAnalytics.py | 2 +- app/pages/UserPage.py | 4 +- docker-compose.yml | 2 + tests/analytics/test_MatchAnalytics.py | 2 +- 10 files changed, 146 insertions(+), 119 deletions(-) create mode 100644 .env-defaults delete mode 100644 .env_example delete mode 100644 BREAKING.md diff --git a/.env-defaults b/.env-defaults new file mode 100644 index 0000000..3f3e81a --- /dev/null +++ b/.env-defaults @@ -0,0 +1,6 @@ +HOST=0.0.0.0 +PORT=8050 +USER_FILE_PATH=change/me/user.json +MATCH_FILE_PATH=change/me/matches.json +ASSETS_PATH=app/assets/ +GEOLITE_DB_PATH=data/GeoLite2-City.mmdb \ No newline at end of file diff --git a/.env_example b/.env_example deleted file mode 100644 index ae95262..0000000 --- a/.env_example +++ /dev/null @@ -1,6 +0,0 @@ -HOST='0.0.0.0' -PORT=8050 -USER_FILE_PATH='file/path/user.json' -MATCH_FILE_PATH='file/path/matches.json' -ASSETS_PATH='app/assets/' -GEOLITE_DB_PATH='data/GeoLite2-City.mmdb' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7f0906c..f895aff 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ data/ .vscode .pytest_cache app/assets/ -scratch/ \ No newline at end of file +screenshots/ \ No newline at end of file diff --git a/BREAKING.md b/BREAKING.md deleted file mode 100644 index af6d7c1..0000000 --- a/BREAKING.md +++ /dev/null @@ -1,11 +0,0 @@ -# Breaking Changes - -This is a comprehensive list of the breaking changes introduced in the different versions of the project. - - -## Versions -- [Version 0.x](#version-0x) - - -## Version 0.x - diff --git a/Dockerfile b/Dockerfile index 4f784de..95ac326 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,4 +17,4 @@ COPY . . EXPOSE 8050 # Command to run the application -CMD ["python", "app/app.py"] \ No newline at end of file +CMD ["python", "app/main.py"] \ No newline at end of file diff --git a/README.md b/README.md index 836a0e8..fabf918 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,133 @@ -## Overview -Hinge allows users to request an export of their personal data that was collected while they were using the app. If you have a Hinge account, you can request your data by going to Settings -> Download My Data. It typically takes between 24 and 48 hours to fulfill this request, and once the data are ready, Hinge provides a `.zip` file with your personal data. +# Hinge Data Analysis +## Purpose +This project analyzes personal data exported from Hinge to provide valuable insights into the user's experiences on the platform. By examining the user's profile, dating preferences, and interactions with other users, the project aims to reveal patterns, trends, and meaningful statistics that enhance the understanding of how users engage with Hinge and make decisions based on their preferences. + +### Getting Your Personal Data +Hinge allows active users to request an export of their personal data that were collected while they have had an account. If you have a Hinge account, you can request your data by going to Settings -> Download My Data. It typically takes between 24 and 48 hours to fulfill this request, and once the data are ready, Hinge provides a `.zip` file with your personal data. -### The Data Export Provided by Hinge -The data export provided by Hinge contains several files, but the main thing is the `index.html` file, which is used to render a web page with tabs showing different data. The tabs provided by Hinge are labeled: User, Matches, Prompts, Media, Subscriptions, Fresh Starts, and Selfie Verification. Aside from viewing changes to your prompts or seeing which pictures you've uploaded, these data are not particularly useful, especially on the Matches tab which should be the most interesting part. - -The Matches tab in the Hinge export contains a list of "Matches", or rather "interactions" as I call them in this project, like this: - -**Match # 1** -2024-01-22 20:13:22 -Like - -**Match # 2** -2024-01-23 20:15:42 -Like - -**Match # 3** -2024-01-23 20:37:27 -Match - -2024-01-23 20:39:45 -Chat: Hello, World! - -2024-01-23 21:49:26 -Remove - -The list of Matches provided by Hinge leaves a lot to be desired, which is why I decided to build this project analyzing and visualizing interesting insights from the Hinge data export. - -## How To Run The App - -### Setting Up GeoLite2 Database -1. Create a free MaxMind account: [MaxMind Signup](https://www.maxmind.com/en/geolite2/signup) -2. Download **GeoLite2-City.mmdb** from [MaxMind](https://www.maxmind.com/en/accounts/current/downloads) -3. Place `GeoLite2-City.mmdb` in the project "data" directory or update the script to point to its location. - - -The application is a multi page Dash Plotly application that runs in a Docker container on port `8050`. Create a Docker build image with: `docker compose build` and run the app with: `docker compose up -d`. The app will be available at [http://0.0.0.0:8050/](http://0.0.0.0:8050/). To bring the container down, use `docker compose down`. - -The page will render with information about the app and instructions on how to use it. - -The "Upload Files" section allows users to upload a `matches.json` or `user.json` file for analysis. **At the moment, the program expects the file to be called `matches.json` or `user.json`, as they are in the export provided by Hinge.** After a file has been selected, it should show the uploaded file name(s) under the upload box. - -[![Screenshot-2024-05-25-at-10-12-48.png](https://i.postimg.cc/KcV1SFcQ/Screenshot-2024-05-25-at-10-12-48.png)](https://postimg.cc/hhLDTkd7) - -The "Data Insights" section contains links to display pages with data related to match data or user data. Click on "Matches" or "Users" to show the information and graphs for either topic. The visualizations will initially show as blank graphs until a file has been upload and the graphs have been reloaded. Clicking the "Reload Graphs" button will regenerate the graphs with the uploaded data. - -## Match Analytics -The match analytics page contains several graphs that show different aspects of the match data. Hinge only provides data on the user's actions for privacy reasons, so most of the data pertains to how the user interacted with other users. - -The first graph is the **Interaction Funnel**, which is a visualization of the different types of interactions that occurred between the user and other users. The outermost part of the funnel "Distinct Interactions" is the total number of unique interactions that occurred. This is a combination of likes the user received and did not reciprocate, likes the user sent and were not reciprocated, and likes the user sent that lead to matches and chats. - -The funnel is a good way to see how many interactions were initiated by the user and how many lead to matches and conversations. - -[![Screenshot-2024-05-25-at-10-17-24.png](https://i.postimg.cc/vHbZdBFr/Screenshot-2024-05-25-at-10-17-24.png)](https://postimg.cc/3WfTX3wN) - -The **Outgoing Likes You've Sent** section contains charts that go into more detail about the user's outgoing likes. The first chart shows users on the app that the user liked more than once. This scenario is perplexing, as it is not clear how this can happen, but does occur infrequently in the data. The second pie chart to the right shows the ratio of how many outgoing likes the user sent with a comment. - -[![Screenshot-2024-05-25-at-10-26-30.png](https://i.postimg.cc/SQwtX2N9/Screenshot-2024-05-25-at-10-26-30.png)](https://postimg.cc/XXkgmv5N) - -Underneath the pie charts, there is a table called **What You're Commenting When You Like Someone's Content**, that shows the comments the user left on other users' profiles when the user liked them. This table is useful for seeing what the user was saying to other users when they liked them. - -The next section **Frequency of Action Types by Day**, shows the frequency of different actions the user took on the app by day. This is useful for seeing patterns of activity and when they were most active on the app. - -[![Screenshot-2024-05-25-at-12-31-35.png](https://i.postimg.cc/nLfN53P0/Screenshot-2024-05-25-at-12-31-35.png)](https://postimg.cc/JsKTH5mk) - -After that, there is a pie chart called **How Many People Did You Give Your Number To?**, which shows exactly that. Of the all the interactions a user had that lead to chats, this graph shows the ratio of how many chats lead to the user giving out their phone number. This operates under the assumption that the user shared their phone number in one of the common formats listed below. - -[![Screenshot-2024-05-25-at-12-36-13.png](https://i.postimg.cc/MpqFmnMF/Screenshot-2024-05-25-at-12-36-13.png)](https://postimg.cc/gntsYkKV) - -The last section of the Match Analytics shows **Outgoing Message per Chat**. This bar graph is a distribution of how many messages were sent by the user in each interaction where messages were exchanged. This is useful for seeing the average length of conversations the user had with others. - -[![Screenshot-2024-05-25-at-12-39-54.png](https://i.postimg.cc/J7jxY1LV/Screenshot-2024-05-25-at-12-39-54.png)](https://postimg.cc/hhPVfRvp) - -## User Analytics -This tab is currently under construction and will be available in a future release. - -## Caveats -Hinge changes and updates the schema of the data export from time to time, and that may or may not break the current analysis code and make things obsolete. So far, I haven't experienced any schema changes that have broken my code, but I assume that over time, changes will occur and things will no longer work. I haven't found a way to stay up to date with their schema changes at this time. - -## Assumptions -Since there is no documentation provided by Hinge, here are some assumptions I am making about the data: -1. Blocks, or "un-matches" (`where block_type = 'remove'`) could go either direction, meaning that block could represent someone removing the match with the user, or it could represent the user removing the block with someone else - 1. I assume this also includes people the user came across while swiping that they wanted to remove from the deck -2. Matches without a like in the same event mean that someone liked the user first, and the user matched with them (i.e. there was no outgoing like sent first) - -## Scenario Matrix -There are several possible scenarios happening in the export data in what Hinge refers to as "matches". These are not all "matches", because some events are simply outgoing likes that were not reciprocated. This is why I refer to them as **interactions**, where an interaction represents the encounters (likes, matches, chats, blocks) that occurred between the user and another person. - -Here are the different scenarios of interactions that occur in the data: - -| Like | Match | Chats | Block | Meaning | -| ---- | ---- | ---- | ---- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| X | | | | The user sent an outgoing, the person did not like them back | -| X | X | X | | The user sent an outgoing like, the other person liked them back, at least one message was exchanged | -| | X | X | | The user received an incoming like, the user liked the other person back and at least one message was exchanged | -| | | | X | The match was removed or "unmatched", can't tell who unmatched who. For some reason, a lot of these exist without any other information and there is no way to tell which interaction it was originally linked to | -| | X | | X | The user received an incoming like, the user liked the other person back, no messages were exchanged, and the match was removed | \ No newline at end of file +## Project Structure +This is the structure of the project and what each section does at a high level. +```bash +app/ +├── analytics/ # Core application logic and data analysis for users and matches +│ ├── __init__.py +│ └── MatchAnalytics.py +│ └── UserAnalytics.py + +├── analytics/ # Contains image files copied from the media folder in the export + +├── pages/ # Visualization rendering and user interface +│ ├── __init__.py +│ └── HomePage.py +│ └── InfoPage.py +│ └── MatchPage.py +│ └── UserPage.py + +├── tools/ # Misc. tools +│ ├── __init__.py +│ └── Logger.py + +├── utilities/ # Helper functions, constants, config, and utilities +│ ├── __init__.py +│ └── DataUtility.py + +tests/ # Unit and integration tests +├── __init__.py +└── analytics +│ ├── __init__.py +│ └── test_MatchAnalytics.py +│ └── test_UserAnalytics.py + +data/ # Local storage for raw data from the personal export +└── export +│ ├── media/ # Images that were uploaded to Hinge +│ └── matches.json +│ └── user.json +│ └── media.json +│ └── prompts.json +│ └── prompt_feedback.json +│ └── selfie_verification.json + +README.md # Project overview and instructions +requirements.txt # Python dependencies +.env # Environment variables (e.g., MATCH_FILE_PATH) +Dockerfile # Dockerfile +docker-compose.yml # Docker Compose configuration +LICENSE # Project license +``` + +## Analysis Breakdown +The application is divided into two main sections, each providing distinct insights into the user's Hinge usage. + +### User Analysis +This section contains insights into how the user's profile is presented, the preferences they've set, and how their interactions shape their experience on the app. + +#### User Uploaded Photos, User Demographics & User Location +These slides show basic user information that was uploaded to Hinge including uploaded photos, demographic information about the user, and information about the user's location. + +*Example visualization* +![User slides](screenshots/user_slides.png) + +#### Profile Information Visibility +Looks at displayed vs. not displayed attributes (ethnicity, religion, workplaces, dating intentions etc.), and helps identify if the user is open vs. private about certain topics. + +*Example visualization* +![Profile Info Vis](screenshots/profile_info_vis.png) + +#### Comparison Between The User and Their Preferences +This shows potential alignment or misalignment between the users profile and their preferences. + +*Example visualization* +![User and Pref Comp](screenshots/com_bet_user_and_prefs.png) + +#### Dating Preferences: Dealbreakers vs Open Choices +This bar chart compares the number of 'dealbreakers' versus 'open' preferences across different dating categories, highlighting which factors are most important or flexible in the user's online dating criteria. + +*Example visualization* +![Dating Prefs](screenshots/dating_prefs.png) + +### Match Analysis +#### Message Count Variability by Month (Last 12 Months) +This box plot shows how the number of messages exchanged per match varies across each month over the past year. + +*Example visualization* +![Message Count Var](screenshots/msg_count_boxplot.png) + +#### Response Latency between Match and First Message Sent +This graph visualizes the response latency, or the time delay between when a match occurs and when the first message is sent. + +*Example visualization* +![Response Latency](screenshots/resp_latency.png) + +#### Duration of Time Between Match and Remove +This histogram visualizes the duration of a connection and when it was removed or blocked. + +*Example visualization* +![Match Rm Duration](screenshots/duration_match_rm.png) + +#### Match Duration vs. Message Count +This scatter plot explores the relationship between the number of messages exchanged in a match and the time until the match was removed or blocked. + +*Example visualization* +![Duration V Count](screenshots/duration_v_count.png) + +## How to Use +1. Export your data from Hinge +2. Install dependencies +(Using a virtual environment is recommended) +`pip install -r requirements.txt` +3. Create a root level folder named `data` and copy the `export` folder from the data export inside the `data` folder. This should contain: +- `media/` +- `user.json` +- `match.json` +All of these are utilized by the project. +4. Create a `.env` file and set environment variables for the following: +- `USER_FILE_PATH` +- `MATCH_FILE_PATH` +Refer to the `.env-defaults` file for details. +5. The Flask app can be run in two ways: + 1. Running the app locally + `python app/main.py` + 2. Running the app with Docker Compose + `docker compose build` + `docker compose up -d` \ No newline at end of file diff --git a/app/analytics/MatchAnalytics.py b/app/analytics/MatchAnalytics.py index ff9565a..9f29b8d 100644 --- a/app/analytics/MatchAnalytics.py +++ b/app/analytics/MatchAnalytics.py @@ -6,7 +6,7 @@ def __init__(self): self.match_file_path = os.environ.get("MATCH_FILE_PATH") if self.match_file_path is None: - raise Exception("MATCH_FILE_PATH environment varviable is not set.") + raise Exception("MATCH_FILE_PATH environment variable is not set.") if '.json' not in self.match_file_path: raise Exception("The match file needs to be a JSON file.") diff --git a/app/pages/UserPage.py b/app/pages/UserPage.py index 599196a..f73e729 100644 --- a/app/pages/UserPage.py +++ b/app/pages/UserPage.py @@ -62,7 +62,7 @@ def stringency_vs_flexibility(): withBorder=True, shadow="sm", radius="md", - style={"height": "520px"}, + style={"height": "600px"}, ) def geolocation(): @@ -174,7 +174,7 @@ def disclosure_vs_privacy(): dmc.Space(h=10), dmc.Text("How much information does this user choose to share vs. keep private?", weight=700, size="xl"), dmc.Space(h=10), - dmc.Text("Looks at displayed vs. not displayed attributes (ethnicity, religion, workplaces, dating intentions etc. and helps identify if the user is open vs. private about certain topics.", size="md"), + dmc.Text("Looks at displayed vs. not displayed attributes (ethnicity, religion, workplaces, dating intentions etc.), and helps identify if the user is open vs. private about certain topics.", size="md"), dmc.Space(h=10), dcc.Graph(figure=fig) ], diff --git a/docker-compose.yml b/docker-compose.yml index 0327c7a..e42e30f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ version: '3.8' services: web: build: . + env_file: + - .env container_name: hinge_analyzer_app ports: - "8050:8050" # Map port 8050 of the host to port 8050 of the container diff --git a/tests/analytics/test_MatchAnalytics.py b/tests/analytics/test_MatchAnalytics.py index fac9e41..86beee8 100644 --- a/tests/analytics/test_MatchAnalytics.py +++ b/tests/analytics/test_MatchAnalytics.py @@ -119,7 +119,7 @@ def test_match_file_path_not_set(): if "MATCH_FILE_PATH" in os.environ: del os.environ["MATCH_FILE_PATH"] - with pytest.raises(Exception, match="MATCH_FILE_PATH environment varviable is not set."): + with pytest.raises(Exception, match="MATCH_FILE_PATH environment variable is not set."): MatchAnalytics() def test_match_file_not_json(): From 4b89b4eb2c055f2c9a2dc75c658a0af810934ac1 Mon Sep 17 00:00:00 2001 From: Shelby Potts Date: Fri, 25 Apr 2025 09:25:02 -0500 Subject: [PATCH 3/3] tests capturing new env vars --- .env-defaults | 1 + tests/analytics/test_UserAnalytics.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.env-defaults b/.env-defaults index 3f3e81a..f833755 100644 --- a/.env-defaults +++ b/.env-defaults @@ -2,5 +2,6 @@ HOST=0.0.0.0 PORT=8050 USER_FILE_PATH=change/me/user.json MATCH_FILE_PATH=change/me/matches.json +MEDIA_PATH=change/me/media/ ASSETS_PATH=app/assets/ GEOLITE_DB_PATH=data/GeoLite2-City.mmdb \ No newline at end of file diff --git a/tests/analytics/test_UserAnalytics.py b/tests/analytics/test_UserAnalytics.py index 07cda14..4504080 100644 --- a/tests/analytics/test_UserAnalytics.py +++ b/tests/analytics/test_UserAnalytics.py @@ -9,6 +9,9 @@ # test values ######################################################################################### USER_FILE_PATH = "fake/file/path/users.json" +GEOLITE_DB_PATH = 'data/db_path.mmdb' +ASSETS_PATH = 'fake/file/path/assets/' +MEDIA_PATH = 'fake/file/path/media/' USER_DATA = ''' { "devices": [ @@ -118,7 +121,6 @@ ''' COUNT_DISPLAYED_ATTRIB_OUTPUT = {'identity': {'true': 2, 'false': 4}, 'lifestyle': {'true': 2, 'false': 3}, 'career': {'true': 2, 'false': 1}, 'future_plans': {'true': 1, 'false': 3}} STRINGENCY_COUNTS = {'physical': {'true': 1, 'false': 1}, 'identity': {'true': 0, 'false': 3}, 'lifestyle': {'true': 0, 'false': 4}, 'career': {'true': 0, 'false': 1}, 'future_plans': {'true': 0, 'false': 2}} -GEOLITE_DB_PATH = 'data/db_path.mmdb' ######################################################################################### # pytest fixtures @@ -127,9 +129,13 @@ def user_analytics(monkeypatch): monkeypatch.setenv("USER_FILE_PATH", USER_FILE_PATH) monkeypatch.setenv("GEOLITE_DB_PATH", GEOLITE_DB_PATH) + monkeypatch.setenv("ASSETS_PATH", ASSETS_PATH) + monkeypatch.setenv("MEDIA_PATH", MEDIA_PATH) with patch("builtins.open", mock_open(read_data=USER_DATA)) as mock_file, \ - patch("json.load", return_value=json.loads(USER_DATA)) as mock_json_load: + patch("json.load", return_value=json.loads(USER_DATA)) as mock_json_load, \ + patch("os.makedirs"), \ + patch("os.listdir", return_value=[]): # prevent actual file ops user_analytics = UserAnalytics() return user_analytics