Skip to content

Commit

Permalink
Merge pull request #5 from johannesE/master
Browse files Browse the repository at this point in the history
Update dependencies and add an hourly forecast
  • Loading branch information
Kociamber authored Mar 3, 2024
2 parents 8708543 + 8251085 commit 2eae18c
Show file tree
Hide file tree
Showing 20 changed files with 458 additions and 78 deletions.
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "mix" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
39 changes: 39 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Reformat elixir
on: push

jobs:
run:
name: Reformat elixir
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- uses: actions/cache@v3
with:
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
- name: Setup elixir
uses: erlef/setup-beam@v1
with:
otp-version: 24.0
elixir-version: 1.13.4
- name: Install mix dependecies
run: mix deps.get

- name: Compile app (required for the formatter plugin)
run: mix compile

- name: Format with mix format
run: mix format

- name: Commit mix format code changes
uses: EndBug/add-and-commit@v9
with:
author_name: "${{ github.event.pusher.name }}"
author_email: "${{ github.event.pusher.email }}"
message: 'Fix formatting of elixir code with mix format'
add: 'lib'
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Test elixir
on: push

jobs:
run:
name: Test elixir
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- uses: actions/cache@v3
with:
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
- name: Setup elixir
uses: erlef/setup-beam@v1
with:
otp-version: 24.0
elixir-version: 1.13.4
- name: Install mix dependecies
run: mix deps.get --only test

- name: mix test
run: mix test
env:
OWM_API_KEY: ${{ secrets.OWM_API_KEY }}
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
elixir 1.9
elixir 1.12.1
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use Mix.Config
config :logger, level: :info
config :ex_owm, api_key: System.get_env("OWM_API_KEY")

config :ex_owm, ExOwm.WeatherCache,
config :ex_owm, ExOwm.Cache,
gc_interval: :timer.hours(1),
max_size: 1_000,
# 20MB
Expand Down
16 changes: 16 additions & 0 deletions lib/ex_owm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ defmodule ExOwm do
def get_five_day_forecast(location, opts) when is_map(location),
do: get_five_day_forecast([location], opts)

@doc """
Gets 4 day hourly forecast data of the given location with specified options.
## Examples
iex> ExOwm.get_hourly_forecast([%{city: "Warsaw"}, %{city: "London", country_code: "uk"}], units: :metric, lang: :pl)
"""
@spec get_hourly_forecast(requests, options) :: map
def get_hourly_forecast(locations, opts \\ [])

def get_hourly_forecast(locations, opts) when is_list(locations),
do: ExOwm.HourlyForecast.Coordinator.get_weather(locations, opts)

def get_hourly_forecast(location, opts) when is_map(location),
do: get_hourly_forecast([location], opts)

@doc """
Gets 1 to 16 days forecast data of the given location with specified options.
Expand Down
12 changes: 10 additions & 2 deletions lib/ex_owm/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@ defmodule ExOwm.Api do
{:ok, %HTTPoison.Response{status_code: 401, body: json_body}} ->
{:error, :api_key_invalid, json_body}

error ->
error
{:ok, response} ->
{:error, :unknown_api_response, response}

{:error, reason} ->
{:error, reason}
end
end

defp parse_json({:ok, json}), do: Jason.decode(json)

defp parse_json({:error, :unknown_api_response, response}),
do: {:error, :unknown_api_response, response}

defp parse_json({:error, reason, json_body}), do: {:error, reason, Jason.decode!(json_body)}
defp parse_json({:error, %HTTPoison.Error{} = reason}), do: {:error, reason}
end
14 changes: 3 additions & 11 deletions lib/ex_owm/current_weather/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ defmodule ExOwm.CurrentWeather.Worker do
"""
@spec get_current_weather(map, key: atom) :: map
def get_current_weather(location, opts) do
case Cache.get("current_weather: #{inspect(location)}") do
# If location wasn't cached within last 10 minutes, call OWM API
nil ->
result = Api.send_and_parse_request(:get_current_weather, location, opts)
Cache.put("current_weather: #{inspect(location)}", result, ttl: :timer.minutes(10))
result

# If location was cached, return it
location ->
location
end
ExOwm.WorkerHelper.get_from_cache_or_call("current_weather: #{inspect(location)}", fn ->
Api.send_and_parse_request(:get_current_weather, location, opts)
end)
end
end
14 changes: 3 additions & 11 deletions lib/ex_owm/five_day_forecast/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ defmodule ExOwm.FiveDayForecast.Worker do
"""
@spec get_five_day_forecast(map, key: atom) :: map
def get_five_day_forecast(location, opts) do
case Cache.get("5_day_forecast: #{inspect(location)}") do
# If location wasn't cached within last 10 minutes, call OWM API
nil ->
result = Api.send_and_parse_request(:get_five_day_forecast, location, opts)
Cache.put("5_day_forecast: #{inspect(location)}", result, ttl: :timer.minutes(10))
result

# If location was cached, return it
location ->
location
end
ExOwm.WorkerHelper.get_from_cache_or_call("five_day_forecast: #{inspect(location)}", fn ->
Api.send_and_parse_request(:get_five_day_forecast, location, opts)
end)
end
end
16 changes: 3 additions & 13 deletions lib/ex_owm/historical_weather/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,8 @@ defmodule ExOwm.HistoricalWeather.Worker do
"""
@spec get_historical_weather(map, key: atom) :: map
def get_historical_weather(location, opts) do
case Cache.get("historical_weather: #{inspect(location)}") do
# If location wasn't cached within last 10 minutes, call OWM API
nil ->
result = Api.send_and_parse_request(:get_historical_weather, location, opts)

# TODO: can we increase this ttl based on the current time? Because it always returns the whole day.
Cache.put("historical_weather: #{inspect(location)}", result, ttl: :timer.minutes(10))
result

# If location was cached, return it
location ->
location
end
ExOwm.WorkerHelper.get_from_cache_or_call("historical_weather: #{inspect(location)}", fn ->
Api.send_and_parse_request(:get_historical_weather, location, opts)
end)
end
end
40 changes: 40 additions & 0 deletions lib/ex_owm/hourly_forecast/coordinator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule ExOwm.HourlyForecast.Coordinator do
@moduledoc """
This module is a GenServer implementation created for handling concurrent
worker task coordination.
"""
use GenServer
alias ExOwm.HourlyForecast.Worker

## Client API
def start_link(options \\ []) do
GenServer.start_link(__MODULE__, %{}, options ++ [name: :hourly_forecast_coordinator])
end

def get_weather(locations, opts) do
GenServer.call(:hourly_forecast_coordinator, {:get_hourly_forecast, locations, opts})
end

## Server implementation
def init(_) do
{:ok, %{}}
end

def handle_call({:get_state}, _from, state) do
{:reply, state, state}
end

def handle_call({:get_hourly_forecast, locations, opts}, _from, _state) do
spawn_worker_tasks(locations, opts)
end

defp spawn_worker_tasks(locations, opts) do
worker_tasks =
Enum.map(locations, fn location ->
Task.async(Worker, :get_hourly_forecast, [location, opts])
end)

results = Enum.map(worker_tasks, fn task -> Task.await(task) end)
{:reply, results, results}
end
end
19 changes: 19 additions & 0 deletions lib/ex_owm/hourly_forecast/worker.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule ExOwm.HourlyForecast.Worker do
@moduledoc """
Five Day Forecast Worker task implementation.
"""
alias ExOwm.Api
alias ExOwm.Cache

Check warning on line 6 in lib/ex_owm/hourly_forecast/worker.ex

View workflow job for this annotation

GitHub Actions / Reformat elixir

unused alias Cache

Check warning on line 6 in lib/ex_owm/hourly_forecast/worker.ex

View workflow job for this annotation

GitHub Actions / Test elixir

unused alias Cache

@doc """
Returns five day weather forecast for a specific location and given options.
Checks whether request has been already cached, if not it sends the request to
OWM API and caches it with specific TTL.
"""
@spec get_hourly_forecast(map, key: atom) :: map
def get_hourly_forecast(location, opts) do
ExOwm.WorkerHelper.get_from_cache_or_call("hourly_forecast: #{inspect(location)}", fn ->
Api.send_and_parse_request(:get_hourly_forecast, location, opts)
end)
end
end
4 changes: 4 additions & 0 deletions lib/ex_owm/request_string.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ defmodule ExOwm.RequestString do
defp add_prefix_substring({:get_five_day_forecast, location, opts}),
do: {"api.openweathermap.org/data/2.5/forecast", location, opts}

# Four day hourly forecast call. https://openweathermap.org/api/hourly-forecast
defp add_prefix_substring({:get_hourly_forecast, location, opts}),
do: {"pro.openweathermap.org/data/2.5/forecast/hourly", location, opts}

# Sixteen day forecast call.
defp add_prefix_substring({:get_sixteen_day_forecast, location, opts}),
do: {"api.openweathermap.org/data/2.5/forecast/daily", location, opts}
Expand Down
14 changes: 3 additions & 11 deletions lib/ex_owm/sixteen_day_forecast/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ defmodule ExOwm.SixteenDayForecast.Worker do
"""
@spec get_sixteen_day_forecast(map, key: atom) :: map
def get_sixteen_day_forecast(location, opts) do
case Cache.get("16_day_forecast: #{inspect(location)}") do
# If location wasn't cached within last 10 minutes, call OWM API
nil ->
result = Api.send_and_parse_request(:get_sixteen_day_forecast, location, opts)
Cache.put("16_day_forecast: #{inspect(location)}", result, ttl: :timer.minutes(10))
result

# If location was cached, return it
location ->
location
end
ExOwm.WorkerHelper.get_from_cache_or_call("sixteen_day_forecast: #{inspect(location)}", fn ->
Api.send_and_parse_request(:get_sixteen_day_forecast, location, opts)
end)
end
end
1 change: 1 addition & 0 deletions lib/ex_owm/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule ExOwm.Supervisor do
ExOwm.CurrentWeather.Coordinator,
ExOwm.Weather.Coordinator,
ExOwm.FiveDayForecast.Coordinator,
ExOwm.HourlyForecast.Coordinator,
ExOwm.SixteenDayForecast.Coordinator,
ExOwm.HistoricalWeather.Coordinator
]
Expand Down
14 changes: 3 additions & 11 deletions lib/ex_owm/weather/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ defmodule ExOwm.Weather.Worker do
"""
@spec get_weather(map, key: atom) :: map
def get_weather(location, opts) do
case Cache.get("one_call: #{inspect(location)}") do
# If location wasn't cached within last 10 minutes, call OWM API
nil ->
result = Api.send_and_parse_request(:get_weather, location, opts)
Cache.put("one_call: #{inspect(location)}", result, ttl: :timer.minutes(10))
result

# If location was cached, return it
location ->
location
end
ExOwm.WorkerHelper.get_from_cache_or_call("one_call: #{inspect(location)}", fn ->
Api.send_and_parse_request(:get_weather, location, opts)
end)
end
end
20 changes: 20 additions & 0 deletions lib/ex_owm/worker_helper.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule ExOwm.WorkerHelper do
@moduledoc false
alias ExOwm.Cache

@spec get_from_cache_or_call(String.t(), fun(), pos_integer()) ::
{:ok, map()} | {:error, map()} | {:error, map(), map()}
def get_from_cache_or_call(cache_key, api_fun, ttl \\ :timer.minutes(10)) do
case Cache.get(cache_key) do
# If location wasn't cached within last ttl minutes, call OWM API
nil ->
result = api_fun.()
Cache.put(cache_key, result, ttl: ttl)
result

# If location was cached, return it
location ->
location
end
end
end
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule ExOwm.Mixfile do
[
app: :ex_owm,
name: "ExOwm",
version: "1.2.3",
version: "1.2.4",
description: "OpenWeatherMap API Elixir client.",
source_url: @github_url,
homepage_url: @github_url,
Expand All @@ -15,7 +15,7 @@ defmodule ExOwm.Mixfile do
licenses: ["MIT"],
links: %{"GitHub" => @github_url}
],
elixir: "~> 1.9",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),
docs: [
Expand All @@ -39,6 +39,7 @@ defmodule ExOwm.Mixfile do
{:httpoison, "~> 1.7"},
{:jason, "~> 1.2"},
{:nebulex, "~> 2.0"},
{:shards, "~> 1.0"},
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false}
]
Expand Down
Loading

0 comments on commit 2eae18c

Please sign in to comment.