diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 50464fc..f8d1ef6 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -17,5 +17,5 @@ jobs: pip install poetry poetry config installer.max-workers 10 poetry install --no-interaction --no-ansi - - name: Run pre-commit - run: poetry run pre-commit run --all-files + - name: Run Lint + run: make lint diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 530c7fa..9070f3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,17 @@ repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.4.6 + - repo: local hooks: # Run the linter. - - id: ruff - entry: ruff check . --fix + - id: lint + name: Lint + entry: make lint + types: [python] + language: system + pass_filenames: false # Run the formatter. - - id: ruff-format - args: [ . ] + - id: format + name: Format + entry: make format + types: [python] + language: system + pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cf58a9..f978e55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,12 +16,10 @@ Once you find an interesting issue let us know that you want to work on it by co ## Development ### Install our development environment -1. Please set up your development environment by referring to the `Setup` section in the `README.md`. +Please set up your development environment by referring to the `Setup` section in the `README.md`. -2. Install the `pre-commit`: - ``` - pre-commit install - ``` +> [!WARNING] +> Make sure to run `make install` to ensure that lint and format are executed reliably when using the `git commit` command. ### Code Style and Quality - The [PEP 8](https://realpython.com/python-pep8/) styling convention is used. diff --git a/Dockerfile b/Dockerfile index aed9f9f..976c622 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,10 @@ -FROM python:3.12.3-slim +FROM python:alpine3.20 # install apt packages (curl command) -RUN apt-get update && \ - apt-get install -y curl && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* +RUN apk add --update-cache curl && \ + apk cache clean && \ + rm -rf /var/cache/apk/* #this instruction specifies the "working directory" #or the path in the image where files will be copied and commands will be executed. diff --git a/README.md b/README.md index c318e0e..70a13db 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ Wave Period: 9.8 | forecast / fc | Number of forecast days. Max = 7, default = 0 | | hide_wave / hw | Hide the default wave art | | show_large_wave / slw | Show the large wave art | +| show_air_temp / sat | Show the air temp | +| show_wind_speed / sws | Show the wind speed | +| show_wind_direction / swd | Show the wind direction | | hide_uv / huv | Hide uv index | | hide_height / hh | Hide surf height | | hide_direction / hdir | Hide Swell direction | @@ -77,17 +80,12 @@ To use cli-surf, clone the project locally and install the necessary dependencie cd cli-surf ``` -3. Install dependencies using Poetry. +3. Install dependencies and Activate the virtual environment. ```bash - poetry install + make install ``` -4. Activate the virtual environment. - ```bash - poetry shell - ``` - -5. Run the project. For example, if the entry point is `server.py`, use the following command. +4. Run the project. For example, if the entry point is `server.py`, use the following command. ```bash python src/server.py diff --git a/docs/cheat_sheet.md b/docs/cheat_sheet.md index 7b343a7..30f6897 100644 --- a/docs/cheat_sheet.md +++ b/docs/cheat_sheet.md @@ -6,7 +6,7 @@ When developing, these commands may come in handy: | Argument | Description| | -------- | ------- | -| `sudo ss -lptn 'sport = :` | List the processes running on port ``. | +| `sudo ss -lptn 'sport = :'` | List the processes running on port ``. | | `sudo kill -9 ` | Kill the process with with id `` | ## [Poetry Commands](https://python-poetry.org/docs/basic-usage/) @@ -18,6 +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 | ## [Mkdocs Commands](https://www.mkdocs.org/user-guide/) diff --git a/docs/styling.md b/docs/styling.md index 72b39fd..39f5a9a 100644 --- a/docs/styling.md +++ b/docs/styling.md @@ -7,4 +7,4 @@ 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 `poetry run pre-commit run --all-files`. diff --git a/makefile b/makefile index b709514..53e4d95 100644 --- a/makefile +++ b/makefile @@ -1,25 +1,68 @@ -.PHONY: run run_docker test test_docker post_test docker_post_test send_email send_email_docker +.DEFAULT_GOAL := all +sources = src tests +.PHONY: install +install: + poetry install + poetry shell + pre-commit install + +.PHONY: run run: poetry run python src/server.py +.PHONY: format +format: + poetry run ruff check --fix $(sources) + poetry run ruff format $(sources) + +.PHONY: lint +lint: + poetry run ruff check $(sources) + poetry run ruff format --check $(sources) + +.PHONY: run_docker run_docker: docker compose up -d +.PHONY: test test: - poetry run pytest -s -x --cov=src -vv + poetry run pytest +.PHONY: test_docker test_docker: - docker compose exec flask poetry run pytest -s -x --cov=src -vv + docker compose exec flask poetry run pytest +.PHONY: output_coverage output_coverage: - poetry run coverage html + @rm -rf htmlcov + @mkdir -p htmlcov + poetry run coverage run -m pytest + poetry run coverage report + poetry run coverage html -d htmlcov +.PHONY: output_coverage_docker output_coverage_docker: - docker compose exec flask poetry run coverage html + @docker compose exec flask rm -rf htmlcov + @docker compose exec flask mkdir -p htmlcov + docker compose exec flask poetry run coverage run -m pytest + docker compose exec flask poetry run coverage report + docker compose exec flask poetry run coverage html -d htmlcov +.PHONY: send_email send_email: poetry run python src/send_email.py +.PHONY: send_email_docker send_email_docker: docker compose exec flask poetry run python src/send_email.py + +.PHONY: clean +clean: + rm -rf htmlcov + rm -rf .pytest_cache + rm -f .coverage + rm -f .coverage.* + +.PHONY: all +all: format lint test diff --git a/poetry.lock b/poetry.lock index abd2394..bb76d63 100644 --- a/poetry.lock +++ b/poetry.lock @@ -339,13 +339,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -2775,13 +2775,13 @@ six = "*" [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -2976,18 +2976,18 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.19.0" +version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.0-py3-none-any.whl", hash = "sha256:96dc6ad62f1441bcaccef23b274ec471518daf4fbbc580341204936a5a3dddec"}, - {file = "zipp-3.19.0.tar.gz", hash = "sha256:952df858fb3164426c976d9338d3961e8e8b3758e2e059e0f754b8c4262625ee"}, + {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, + {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" diff --git a/pyproject.toml b/pyproject.toml index 730ebfe..8517cb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,8 +41,10 @@ build-backend = "poetry.core.masonry.api" # pytest settings [tool.pytest.ini_options] +testpaths = 'tests' pythonpath = "." addopts = '-p no:warnings' # disable pytest warnings +log_format = '%(name)s %(levelname)s: %(message)s' # ruff global settings [tool.ruff] diff --git a/src/api.py b/src/api.py index 07b1b71..5eae1d2 100644 --- a/src/api.py +++ b/src/api.py @@ -118,6 +118,37 @@ def ocean_information(lat, long, decimal, unit="imperial"): return [current_wave_height, current_wave_direction, current_wave_period] +def current_wind_temp(lat, long, decimal, temp_unit="fahrenheit"): + """ + Gathers the wind and temperature data + """ + # Setup the Open-Meteo API client with cache and retry on error + cache_session = requests_cache.CachedSession(".cache", expire_after=3600) + retry_session = retry(cache_session, retries=5, backoff_factor=0.2) + openmeteo = openmeteo_requests.Client(session=retry_session) + + url = "https://api.open-meteo.com/v1/forecast" + params = { + "latitude": lat, + "longitude": long, + "current": ["temperature_2m", "wind_speed_10m", "wind_direction_10m"], + "temperature_unit": temp_unit, + "wind_speed_unit": "mph", + "precipitation_unit": "inch", + } + responses = openmeteo.weather_api(url, params=params) + + response = responses[0] + + # Current values. The order of variables needs to be the same as requested. + current = response.Current() + current_temperature = round(current.Variables(0).Value(), decimal) + current_wind_speed = round(current.Variables(1).Value(), decimal) + current_wind_direction = round(current.Variables(2).Value(), decimal) + + return current_temperature, current_wind_speed, current_wind_direction + + def forecast(lat, long, decimal, days=0): """ Number of forecast days. Max is 7 @@ -184,6 +215,9 @@ def gather_data(lat, long, arguments): ) uv_index = get_uv(lat, long, arguments["decimal"], arguments["unit"]) + wind_temp = current_wind_temp(lat, long, arguments["decimal"]) + air_temp, wind_speed, wind_dir = wind_temp[0], wind_temp[1], wind_temp[2] + arguments["ocean_data"] = ocean_data arguments["uv_index"] = uv_index spot_forecast = forecast(lat, long, arguments["decimal"], 7) @@ -196,9 +230,12 @@ def gather_data(lat, long, arguments): "Long": long, "Location": arguments["city"], "Height": ocean_data[0], - "Direction": ocean_data[1], + "Swell Direction": ocean_data[1], "Period": ocean_data[2], "UV Index": uv_index, + "Air Temperature": air_temp, + "Wind Speed": wind_speed, + "Wind Direction": wind_dir, "Forecast": json_forecast, "Unit": arguments["unit"], } diff --git a/src/helper.py b/src/helper.py index 217ca45..4d82d33 100644 --- a/src/helper.py +++ b/src/helper.py @@ -27,6 +27,9 @@ def arguments_dictionary(lat, long, city, args): "show_period": 1, "show_city": 1, "show_date": 1, + "show_air_temp": 0, + "show_wind_speed": 0, + "show_wind_direction": 0, "json_output": 0, "unit": "imperial", "decimal": extract_decimal(args), @@ -39,7 +42,7 @@ def arguments_dictionary(lat, long, city, args): return arguments -def set_output_values(args, arguments): +def set_output_values(args, arguments): # noqa """ Takes a list of command line arguments(args) and sets the appropritate values @@ -68,6 +71,13 @@ def set_output_values(args, arguments): arguments["json_output"] = 1 if "gpt" in args or "g" in args: arguments["gpt"] = 1 + if "show_air_temp" in args or "sat" in args: + arguments["show_air_temp"] = 1 + if "show_wind_speed" in args or "sws" in args: + arguments["show_wind_speed"] = 1 + if "show_wind_direction" in args or "swd" in args: + arguments["show_wind_direction"] = 1 + return arguments @@ -106,18 +116,24 @@ def print_location(city, show_city): print("\n") -def print_ocean_data(ocean_data_dict): +def print_ocean_data(arguments_dict, ocean_data_dict): """ Prints ocean data(height, wave direction, period, etc) """ - if int(ocean_data_dict["show_uv"]) == 1: - print("UV index: ", ocean_data_dict["uv_index"]) - if int(ocean_data_dict["show_height"]) == 1: - print("Wave Height: ", ocean_data_dict["ocean_data"][0]) - if int(ocean_data_dict["show_direction"]) == 1: - print("Wave Direction: ", ocean_data_dict["ocean_data"][1]) - if int(ocean_data_dict["show_period"]) == 1: - print("Wave Period: ", ocean_data_dict["ocean_data"][2]) + if int(arguments_dict["show_uv"]) == 1: + print("UV index: ", ocean_data_dict["UV Index"]) + if int(arguments_dict["show_height"]) == 1: + print("Wave Height: ", ocean_data_dict["Height"]) + if int(arguments_dict["show_direction"]) == 1: + print("Wave Direction: ", ocean_data_dict["Swell Direction"]) + if int(arguments_dict["show_period"]) == 1: + print("Wave Period: ", ocean_data_dict["Period"]) + if int(arguments_dict["show_air_temp"]) == 1: + print("Air Temp: ", ocean_data_dict["Air Temperature"]) + if int(arguments_dict["show_wind_speed"]) == 1: + print("Wind Speed: ", ocean_data_dict["Wind Speed"]) + if int(arguments_dict["show_wind_direction"]) == 1: + print("Wind Direction: ", ocean_data_dict["Wind Direction"]) def print_forecast(ocean, forecast): @@ -203,7 +219,7 @@ def print_outputs(city, data_dict, arguments, gpt_prompt, gpt_info): arguments["color"], ) # Prints(Height: <>, Period: <>, etc.) - print_ocean_data(arguments) + print_ocean_data(arguments, data_dict) print("\n") forecast = api.forecast( data_dict["Lat"], diff --git a/src/static/script.js b/src/static/script.js index 6331647..ba7b48f 100644 --- a/src/static/script.js +++ b/src/static/script.js @@ -3,6 +3,9 @@ cancelbutton.addEventListener("click",function(){ document.getElementById("curlInput").value =""; }); +const elLoadingDiv = document.getElementById("loadingDiv"); +elLoadingDiv.style.display = "none"; + document.getElementById("reportForm").addEventListener("submit", function(event) { event.preventDefault(); // Prevent default form submission @@ -42,6 +45,8 @@ function httpGetAsync(theUrl, callback) { document.addEventListener("submit", function() { // Function to make the HTTP GET request and update the HTML content // Function to make the HTTP GET request and update the HTML content + + handleLoadingChange() function fetchDataAndUpdateHTML() { // Make the HTTP GET request // Get the value of the location input field @@ -67,15 +72,27 @@ document.addEventListener("submit", function() { // Update the content of the serverResponse div with the extracted data document.getElementById('serverResponse').innerHTML = extractedData; + handleLoadingChange() }) .catch(error => { console.error('Error fetching data:', error); + handleLoadingChange() }); } // Call the function to fetch data and update HTML content when the form is submitted fetchDataAndUpdateHTML(); }); +function handleLoadingChange() { + var serverResponse = document.getElementById("serverResponse"); + if (elLoadingDiv.style.display === "none") { + elLoadingDiv.style.display = "block"; + serverResponse.style.display = "none" + } else { + elLoadingDiv.style.display = "none"; + serverResponse.style.display = "block" + } +} diff --git a/src/templates/index.html b/src/templates/index.html index 9ca3a32..469f3a8 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -74,6 +74,15 @@
+
+
+ +
+ +
+