diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f978e55..f02a18c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,7 +25,7 @@ Please set up your development environment by referring to the `Setup` section i
- The [PEP 8](https://realpython.com/python-pep8/) styling convention is used.
- This is achieved using the `ruff` Linter and Formatter.
- The Linter and Formatter are automatically executed before committing via pre-commit.
- - If you want to run the Linter and Formatter at any time, execute `pre-commit run --all-files`.
+ - If you want to run the Linter and Formatter at any time, execute `pre-commit run --all-files`. Or, `make format` and `make run` can be ran.
### Testing
> [!NOTE]
diff --git a/README.md b/README.md
index 70a13db..c8c95ac 100644
--- a/README.md
+++ b/README.md
@@ -162,7 +162,9 @@ Note that the Flask server must be running in order to send emails.
-Although this application was made with the cli in mind, there is a frontend.
+Although this application was made with the cli in mind, there are two frontends (experimenting at the moment).
+
+**HTML/JS/CSS Frontend**
`http://localhost:8000/home` **or** `:/home` if the application is running on a different host or you have changed the default port.
@@ -170,6 +172,14 @@ You may need to change `IP_ADDRESS` in `.env` to match the ip of the host runnin
Now, running `python3 server.py` will launch the website!
+**Streamlit Frontend**
+
+[Streamlit](https://streamlit.io/) is also an option that we are experimenting with.
+
+To run streamlit: `streamlit run src/dev_streamlit.py`
+
+You will be able to find the frontend here: `http://localhost:8502`
+
## 📈 Contributing
diff --git a/docs/cheat_sheet.md b/docs/cheat_sheet.md
index 30f6897..4bda305 100644
--- a/docs/cheat_sheet.md
+++ b/docs/cheat_sheet.md
@@ -18,7 +18,7 @@ When developing, these commands may come in handy:
| `poetry add ` | Add a new dependency to Poetry |
| `poetry add --group dev ` | Add a new developer dependency to Poetry |
| `poetry show` | List all available dependencies with descriptions |
-| `poetry run pre-commit run --all-files` | Run the Linter & Formatter |
+| `pre-commit run --all-files` | Run the Linter & Formatter |
## [Mkdocs Commands](https://www.mkdocs.org/user-guide/)
@@ -30,12 +30,16 @@ When developing, these commands may come in handy:
| Argument | Description|
| -------- | ------- |
+| `make install` | Install dependencies and activates the virtual environment. |
| `make run` | Runs `server.py` |
| `make run_docker` | Runs `docker compose up -d` |
| `make test` | Runs pytest |
| `make test_docker` | Runs pytest on Docker |
| `make output_coverage` | Outputs the coverage of the tests |
| `make send_email` | Runs `send_email.py` |
+| `make lint` | Runs the ruff linter |
+| `make format` | Runs the ruff formatter |
+| `make clean` | Cleans up files generated during testing (`.coverage`, `pytest_cache`, etc.) |
## [Git](https://education.github.com/git-cheat-sheet-education.pdf)
@@ -51,4 +55,5 @@ When developing, these commands may come in handy:
| `git checkout -b ` | Creates a new branch, `branch`, and switches into it |
| `git branch -d ` | Delete a local branch |
| `git push -u origin ` | Pushes a local branch to the upstream remote repo |
-| `git log --branches --not --remotes` | View commits that have not yet been pushed |
\ No newline at end of file
+| `git log --branches --not --remotes` | View commits that have not yet been pushed |
+| `git fetch origin pull/ID/head:BRANCH_NAME` | Checking out a PR branch with `ID` and `BRANCH_NAME` |
\ No newline at end of file
diff --git a/docs/structure.md b/docs/structure.md
index 031e1e3..0147e46 100644
--- a/docs/structure.md
+++ b/docs/structure.md
@@ -28,6 +28,9 @@ More in-depth structure:
.
├── compose.yaml
├── CONTRIBUTING.md
+├── dist
+│  ├── cli_surf-0.1.0-py3-none-any.whl
+│  └── cli_surf-0.1.0.tar.gz
├── Dockerfile
├── docs
│  ├── cheat_sheet.md
@@ -55,6 +58,7 @@ More in-depth structure:
│  ├── api.py
│  ├── art.py
│  ├── cli.py
+│  ├── dev_streamlit.py
│  ├── gpt.py
│  ├── helper.py
│  ├── __init__.py
@@ -63,6 +67,7 @@ More in-depth structure:
│  ├── server.py
│  ├── settings.py
│  ├── static
+│  ├── streamlit_helper.py
│  └── templates
└── tests
├── __init__.py
@@ -73,6 +78,6 @@ More in-depth structure:
├── test_helper.py
└── test_server.py
-9 directories, 38 files
+10 directories, 42 files
```
diff --git a/docs/styling.md b/docs/styling.md
index 39f5a9a..5e55032 100644
--- a/docs/styling.md
+++ b/docs/styling.md
@@ -7,4 +7,6 @@ This is achieved using the ruff Linter and Formatter.
The Linter and Formatter are automatically executed before committing via pre-commit.
-If you want to run the Linter and Formatter at any time, execute `poetry run pre-commit run --all-files`.
+If you want to run the Linter and Formatter at any time, execute `pre-commit run --all-files`.
+
+You may also run `make lint` or `make format` to run the linter/formatter on its own.
diff --git a/src/api.py b/src/api.py
index 641ef06..12b730f 100644
--- a/src/api.py
+++ b/src/api.py
@@ -16,8 +16,9 @@
def get_coordinates(args):
"""
- Takes a location(city or address) and returns the coordinates: [lat, long]
- If no location is specified, default_location() finds the users coordinates
+ Takes a location (city or address) and returns the coordinates: [lat, long]
+ If no location is specified or the location is invalid, default_location()
+ finds the user's coordinates.
"""
for arg in args:
arg_str = str(arg)
@@ -27,13 +28,18 @@ def get_coordinates(args):
location = geolocator.geocode(address)
if location is not None:
return [location.latitude, location.longitude, location]
- return "No data"
+ else:
+ print(
+ f"Invalid location '{address}' provided. "
+ "Using default location."
+ )
+ return default_location()
return default_location()
def default_location():
"""
- If no location specified in cli, find users location
+ If no location specified in cli, find user's location
Make a GET request to the API endpoint
"""
response = requests.get("https://ipinfo.io/json", timeout=10)
diff --git a/src/cli.py b/src/cli.py
index 191fb91..3f2a12b 100644
--- a/src/cli.py
+++ b/src/cli.py
@@ -15,12 +15,15 @@
gpt_info = [api_key, model]
-def run(lat=0, long=0):
+def run(lat=0, long=0, args=None):
"""
Main function
"""
# Seperates the cli args into a list
- args = helper.seperate_args(sys.argv)
+ if args is None:
+ args = helper.seperate_args(sys.argv)
+ else:
+ args = helper.seperate_args(args)
# return coordinates, lat, long, city
location = api.seperate_args_and_get_location(args)
@@ -41,10 +44,12 @@ def run(lat=0, long=0):
# Non-JSON output
if arguments["json_output"] == 0:
- helper.print_outputs(
+ # Response prints all the outputs & returns the GPT response
+ response = helper.print_outputs(
city, ocean_data_dict, arguments, gpt_prompt, gpt_info
)
- return ocean_data_dict
+ # Returns ocean data, GPT response
+ return ocean_data_dict, response
else:
# print the output in json format!
json_output = helper.json_output(ocean_data_dict)
diff --git a/src/dev_streamlit.py b/src/dev_streamlit.py
new file mode 100644
index 0000000..05ebb22
--- /dev/null
+++ b/src/dev_streamlit.py
@@ -0,0 +1,86 @@
+import sys
+import time
+from pathlib import Path
+
+import streamlit as st
+from streamlit_folium import st_folium
+
+sys.path.append(str(Path(__file__).parent.parent))
+
+from src import streamlit_helper as sl_help
+
+# NOTE: This file is for testing purposes. Do not use it in production.
+
+# Page Configuration ###
+title = "cli-surf"
+st.set_page_config(
+ page_title=title,
+ page_icon="🌊",
+ layout="wide",
+)
+st.title(title)
+
+# Page Content ###
+
+# sidebar
+st.sidebar.markdown(
+ """
+# MENU
+- [weather](#weather)
+- [csv upload](#csv-upload)
+- [graph](#graph)
+- [data sheet](#data-sheet)
+""",
+ unsafe_allow_html=True,
+)
+
+latest_iteration = st.empty()
+bar = st.progress(0)
+
+for i in range(100):
+ latest_iteration.text(f"Iteration {i + 1}")
+ bar.progress(i + 1)
+ time.sleep(0.01)
+
+st.caption("Enter a surf spot to see the map and forecast!")
+
+
+# Toggles in a horizontal line
+col1, col2 = st.columns(2)
+
+with col1:
+ gpt = st.toggle("Activate GPT")
+with col2:
+ map = st.toggle("Show Map", value=True)
+
+extra_args = sl_help.extra_args(gpt)
+
+# User input location
+location = st.text_input("Surf Spot", placeholder="Enter surf spot!")
+
+graph_type = st.radio(
+ "Choose graph type",
+ ["Height/Period :ocean:", "Direction :world_map:"],
+ index=None,
+)
+
+# Checks if location has been entered.
+# If True, gathers surf report and displays map
+if location:
+ get_report = sl_help.get_report(location, extra_args)
+ report_dict, gpt_response, lat, long = get_report
+
+ # Displays the map
+ # TODO: Configure map to only show nearby surf spots based on the lat/long
+ if map:
+ map_data = sl_help.map_data(lat, long)
+ st_folium(map_data, width=725)
+
+ # Writes the GPT response
+ if gpt_response is not None:
+ st.write(gpt_response)
+
+ # Displays the line graph
+ st.write("# Surf Conditions")
+ df = sl_help.graph_data(report_dict, graph_type)
+ st.line_chart(df.rename(columns={"date": "index"}).set_index("index"))
diff --git a/src/helper.py b/src/helper.py
index 66387e0..fa65303 100644
--- a/src/helper.py
+++ b/src/helper.py
@@ -232,9 +232,11 @@ def print_outputs(city, data_dict, arguments, gpt_prompt, gpt_info):
# Prints the forecast(if activated in CLI args)
print_forecast(arguments, forecast)
# Checks if GPT in args, prints GPT response if True
+ gpt_response = None
if arguments["gpt"] == 1:
gpt_response = print_gpt(data_dict, gpt_prompt, gpt_info)
print(gpt_response)
+ return gpt_response
def set_location(location):
diff --git a/src/streamlit_helper.py b/src/streamlit_helper.py
new file mode 100644
index 0000000..9703981
--- /dev/null
+++ b/src/streamlit_helper.py
@@ -0,0 +1,90 @@
+"""
+Helper functions for the streamlit frontend
+"""
+
+import sys
+from pathlib import Path
+
+import folium
+import pandas as pd
+
+sys.path.append(str(Path(__file__).parent.parent))
+
+from src import cli
+
+
+def extra_args(gpt):
+ """
+ By default, the location is the only argument when cli.run()
+ is ran. Extra args outputs and other arguments the user wants,
+ like using the GPT function
+ """
+ # Arguments
+ extra_args = ""
+
+ if gpt:
+ extra_args += ",gpt"
+
+ return extra_args
+
+
+def get_report(location, extra_args):
+ """
+ Executes cli.run(), retrns the report dictionary,
+ gpt response, lat and long
+ """
+ gpt_response = None
+ args = "location=" + location
+ if extra_args:
+ args += extra_args
+ surf_report = cli.run(args=["placeholder", args])
+ report_dict, gpt_response = surf_report[0], surf_report[1]
+ lat, long = report_dict["Lat"], report_dict["Long"]
+
+ return report_dict, gpt_response, lat, long
+
+
+def map_data(lat, long):
+ """
+ Gathers data for the map
+ Docs: https://folium.streamlit.app/
+ """
+ m = folium.Map(location=[lat, long], zoom_start=16)
+ folium.Marker(
+ [lat, long], popup="Surf Spot!", tooltip="Get out there!"
+ ).add_to(m)
+
+ return m
+
+
+def graph_data(report_dict, graph_type="Height/Period :ocean:"):
+ """
+ Gathers the forecasted dates, heights, period and stores them in a pandas
+ dataframe. Will be used to display the line chart
+ """
+ forecasted_dates = [
+ forecast["date"] for forecast in report_dict["Forecast"]
+ ]
+ forecasted_heights = [
+ forecast["height"] for forecast in report_dict["Forecast"]
+ ]
+ forecasted_periods = [
+ forecast["period"] for forecast in report_dict["Forecast"]
+ ]
+ forecasted_directions = [
+ forecast["direction"] for forecast in report_dict["Forecast"]
+ ]
+ # table
+ if graph_type == "Height/Period :ocean:" or graph_type is None:
+ df = pd.DataFrame({
+ "date": forecasted_dates,
+ "heights": forecasted_heights,
+ "periods": forecasted_periods,
+ })
+ else:
+ df = pd.DataFrame({
+ "date": forecasted_dates,
+ "directions": forecasted_directions,
+ })
+
+ return df
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 867d289..edea3a5 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -18,6 +18,6 @@ def test_cli_output():
# Hardcode lat and long for location.
# If not, when test are ran in Github Actions
# We get an error(because server probably isn't near ocean)
- data_dict = cli.run(36.95, -121.97)
+ data_dict = cli.run(36.95, -121.97)[0]
time.sleep(5)
assert len(data_dict) >= expected