Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support One Call API 3.0 #9

Merged
merged 2 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.0', '3.2']
ruby-version: ['2.6', '2.7', '3.0', '3.2', '3.3']

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
AllCops:
TargetRubyVersion: 3.0
TargetRubyVersion: 2.6

Metrics/BlockLength:
Exclude:
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [Unreleased]

## 0.2.0
- Upgrade to Open Weather One Call API 3.0 through configuration option (#8)

## 0.1.6
- Relax required Ruby Version

Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
![RSpec](https://github.com/qurasoft/open_weather_client/actions/workflows/ruby.yml/badge.svg)

Welcome to Open Weather Client.
This gem allows you to easily request weather information from OpenWeatherMap.
This gem allows you to easily request weather information from Open Weather.
It integrates in your rails project, when you are using bundler or even in plain ruby projects.

## Installation
Expand All @@ -24,7 +24,7 @@ Or install it yourself as:
$ gem install open_weather_client

## Usage
During configuration the OpenWeatherMap API key must be set. Afterwards it is as simple as calling `OpenWeatherClient.current(lat:, lon:)` to get the current weather at a location.
During configuration the Open Weather API key must be set. Afterwards it is as simple as calling `OpenWeatherClient.current(lat:, lon:)` to get the current weather at a location.

```ruby
# OpenWeatherClient initializer
Expand All @@ -36,21 +36,40 @@ end
OpenWeatherClient::Weather.current(lat: 50.3569, lon: 7.5890)
```

### Used Open Weather API version
Open Weather provides One Call API 3.0 and API 2.5, which is expected to be deprecated in June 2024.
Until API 2.5 is deprecated it is the default. With Open Weather Client version 1.0 the default will change to One Call API 3.0.

The used API version is configurable through `OpenWeatherClient.configuration.api_version`.

```ruby
# OpenWeatherClient initializer
OpenWeatherClient.configure do |config|
config.api_version = :v25 # (default) also supports :v30
end
```

### Exceptions during requests
When an error occurs during a request, an exception is raised.
If the request is not authorized `OpenWeatherClient::AutheniticationError` is raied.
When attributes like latitude or longitude are outside of the expected range a `RangeError` is raised.

If the configured API version is not supported by the client, `OpenWeatherClient::APIVersionNotSupportedError` is raised.
If the request is not authorized, `OpenWeatherClient::AutheniticationError` is raised.
If attributes like latitude or longitude are outside of the expected range, `RangeError` is raised.
If the time is longer in the past than one hour, `OpenWeatherClient::NotCurrentError` is raised.

### Secure Configuration
In Rails provides the credentials functionality for [environmental security](https://edgeguides.rubyonrails.org/security.html#environmental-security). This mechanism can be used by OpenWeatherClient to load the API key from an encrypted file. This also allows easy separation of production and development channel configuration.
Rails provides the credentials functionality for [environmental security](https://edgeguides.rubyonrails.org/security.html#environmental-security).
This mechanism can be used by OpenWeatherClient to load the API key from an encrypted file.
This also allows easy separation of production and development api key configuration.

All settings are defined under the top-level entry `open_weather_client`.
```yaml
# $ bin/rails credentials:edit
open_weather_client:
appid: "<INSERT OPENWEATHERMAP API KEY HERE>"
```

After configuration of the credentials you can load the settings in your initializer with `#load_from_rails_configuration`.
After configuring the credentials, they can be loaded in the initializer with `#load_from_rails_configuration`.

```ruby
# OpenWeatherClient initializer
Expand Down
5 changes: 2 additions & 3 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ require 'open_weather_client'

OpenWeatherClient.configure do |config|
if File.exist?('config.yml')
# NOTE(Keune): The channels available in the console can be set in the file channels.yml. It contains a simple
# mapping of channel identifiers to webhook URLs. The channel identifier is loaded as a symbol.
# NOTE(Keune): The console automatically loads the appid from the config.yml file in the same directory
#
# Example: APPID: "<INSERT YOUR WEBHOOK URL HERE>"
puts 'Load channels specified by file'
puts 'Load appid specified by file'

require 'yaml'
settings = YAML.load_file 'config.yml'
Expand Down
5 changes: 4 additions & 1 deletion lib/open_weather_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
##
# Get weather data from OpenWeatherMap
module OpenWeatherClient
# The configured Open Weather API Version is not supported
class APIVersionNotSupportedError < StandardError; end

# Error during authentication with the OpenWeatherMap servers
class AuthenticationError < StandardError; end

# The rquested time is not current enough for getting weather data from the OpenWeatherMap server
# The requested time is not current enough for getting weather data from the OpenWeatherMap server
class NotCurrentError < StandardError; end

class << self
Expand Down
4 changes: 2 additions & 2 deletions lib/open_weather_client/caching/memory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ class Caching
# When the limit is reached the least recently used entry is evicted.
class Memory < OpenWeatherClient::Caching
# Memory cache to store a hash of keys and request data
attr :memory_cache
attr_reader :memory_cache
# Key registry of the memory cache. The first entry is the key of the least recently accessed data
attr :memory_keys
attr_reader :memory_keys

##
# Initialize an empty cache
Expand Down
3 changes: 3 additions & 0 deletions lib/open_weather_client/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module OpenWeatherClient
##
# Configuratin of OpenWeatherClient
class Configuration
# [String] Used api version. One of :v25, :v30. Default :v25
attr_accessor :api_version
# [String] API key to access OpenWeatherMap
attr_accessor :appid
# Caching method. One of :none, :memory
Expand All @@ -24,6 +26,7 @@ class Configuration
##
# Initialize a new Configuration with the default settings
def initialize
@api_version = :v25
@caching = OpenWeatherClient::Caching.new
@lang = 'de'
@max_memory_entries = 10_000
Expand Down
14 changes: 13 additions & 1 deletion lib/open_weather_client/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Request
# @param lon[Float] longitude of the requests location
# @param time[Time] time of the request
#
# @raise [APIVersionNotSupportedError] if the configured api version is not supported
# @raise [AuthenticationError] if the request is not authorized, e.g in case the API key is not correct
# @raise [NotCurrentError] if the requested time is older than 1 hour
#
Expand All @@ -21,7 +22,7 @@ def self.get(lat:, lon:, time: Time.now)
raise OpenWeatherClient::NotCurrentError if time < Time.now - 1 * 60 * 60

begin
response = connection(lat, lon).get('2.5/weather')
response = connection(lat, lon).get(path)
OpenWeatherClient.cache.store(lat: lat, lon: lon, data: response.body, time: time)
rescue Faraday::UnauthorizedError
raise OpenWeatherClient::AuthenticationError
Expand Down Expand Up @@ -54,5 +55,16 @@ def self.params(lat, lon)
units: OpenWeatherClient.configuration.units
}
end

def self.path
case OpenWeatherClient.configuration.api_version
when :v25
'2.5/weather'
when :v30
'3.0/onecall'
else
raise OpenWeatherClient::APIVersionNotSupportedError
end
end
end
end
2 changes: 1 addition & 1 deletion lib/open_weather_client/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
module OpenWeatherClient
##
# Version of OpenWeatherClient
VERSION = '0.1.6'
VERSION = '0.2.0'
end
1 change: 1 addition & 0 deletions spec/open_weather_client/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def self.application
subject { OpenWeatherClient.configuration }

it 'has a default configuration' do
is_expected.to have_attributes(api_version: :v25)
is_expected.to have_attributes(appid: nil)
is_expected.to have_attributes(caching: OpenWeatherClient::Caching)
is_expected.to have_attributes(units: 'metric')
Expand Down
41 changes: 34 additions & 7 deletions spec/open_weather_client/request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,48 @@
require 'open_weather_client/caching/memory'

RSpec.describe OpenWeatherClient::Request do
describe 'current weather' do
let(:appid) { '1234567890' }
let(:lat) { 50.3569 }
let(:lon) { 7.5890 }

subject { OpenWeatherClient::Request.get(lat: lat, lon: lon) }

before :each do
OpenWeatherClient.configuration.appid = appid
end

context 'when One Call API 3.0' do
before :each do
OpenWeatherClient.configuration.appid = '1234567890'
stub_request(:get, 'https://api.openweathermap.org/data/2.5/weather').with(query: hash_including(:appid, :lat, :lon, :lang, :units)).to_return(body: File.open('./spec/fixtures/weather/current.json'))
OpenWeatherClient.configuration.api_version = :v30
stub_request(:get, 'https://api.openweathermap.org/data/3.0/onecall').with(query: hash_including(:appid, :lat, :lon, :lang, :units)).to_return(body: File.open('./spec/fixtures/weather/current.json'))
end

it 'requests the current weather from correct endpoint' do
subject

expect(WebMock).to have_requested(:get, 'https://api.openweathermap.org/data/3.0/onecall').with query: { appid: appid, lat: lat, lon: lon, lang: OpenWeatherClient.configuration.lang, units: OpenWeatherClient.configuration.units }
end
end

let(:lat) { 50.3569 }
let(:lon) { 7.5890 }
context 'when unsupported API version' do
before :each do
OpenWeatherClient.configuration.api_version = :v666
end

subject { OpenWeatherClient::Request.get(lat: lat, lon: lon) }
it 'raises not supported error' do
expect { subject }.to raise_error OpenWeatherClient::APIVersionNotSupportedError
end
end

describe 'current weather with default configuration' do
before :each do
stub_request(:get, 'https://api.openweathermap.org/data/2.5/weather').with(query: hash_including(:appid, :lat, :lon, :lang, :units)).to_return(body: File.open('./spec/fixtures/weather/current.json'))
end

it 'requests the current weather' do
subject

expect(WebMock).to have_requested(:get, 'https://api.openweathermap.org/data/2.5/weather').with query: { appid: OpenWeatherClient.configuration.appid, lat: lat, lon: lon, lang: OpenWeatherClient.configuration.lang, units: OpenWeatherClient.configuration.units }
expect(WebMock).to have_requested(:get, 'https://api.openweathermap.org/data/2.5/weather').with query: { appid: appid, lat: lat, lon: lon, lang: OpenWeatherClient.configuration.lang, units: OpenWeatherClient.configuration.units }
end

it 'has user agent' do
Expand Down
Loading