Skip to content

Commit

Permalink
Merge branch 'develop' into 160_weather_alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
csparpa committed Jul 18, 2018
2 parents 60a02a3 + c69bf0c commit 336933f
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 29 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ A Python wrapper around the OpenWeatherMap API
[![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/csparpa)

## What is it?
PyOWM is a client Python wrapper library for the OpenWeatherMap (OWM) web API.
PyOWM is a client Python wrapper library for OpenWeatherMap (OWM) web APIs.

It allows quick and easy consumption of OWM weather data from Python applications via a simple object model and in a human-friendly fashion.
It allows quick and easy consumption of OWM data from Python applications via a simple object model and in a human-friendly fashion.

PyOWM runs on Python 2.7 and Python 3.4+ (but watch out! Python 2.x will eventually be dropped - [check details out](https://github.com/csparpa/pyowm/wiki/Timeline-for-dropping-Python-2.x-support))

Expand Down Expand Up @@ -39,16 +39,16 @@ $ pip install git+https://github.com/csparpa/pyowm.git@develop

### API key

As the OpenWeatherMap API needs a valid API key to allow responses,
*PyOWM won't work if you don't provide one*. This stands for both the free and paid (pro) subscription plans.
As OpenWeatherMap APIs need a valid API key to allow responses,
*PyOWM won't work if you don't provide one*. This stands for both free and paid (pro) subscription plans.

You can signup for a free API key [on the OWM website](https://home.openweathermap.org/users/sign_up)

Please notice that the free API subscription plan is subject to requests throttling.

### Examples

That's what you can do with PyOWM and a free OWM API Key:
That's a simple example of what you can do with PyOWM and a free OWM API Key:

```python
import pyowm
Expand Down Expand Up @@ -93,7 +93,7 @@ More PyOWM usage examples are available [here](https://github.com/csparpa/pyowm/
## Documentation
Each release has its own [changelog](https://github.com/csparpa/pyowm/wiki/Changelog).

The library API documentation is available on [Read the Docs](https://pyowm.readthedocs.org/en/stable/).
The library software API documentation is available on [Read the Docs](https://pyowm.readthedocs.org/en/stable/).


## Contributing
Expand Down
2 changes: 2 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
coverage==3.7.1
pytest==3.0.3
recommonmark==0.5.4
Sphinx==1.2.1
sphinx-readable-theme==1.3.0
tox==1.9.2
virtualenv==12.0.7
twine==1.11.0
Empty file removed pyowm/docs/__init__.py
Empty file.
10 changes: 4 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,17 @@
setup(
name='pyowm',
version=constants.PYOWM_VERSION,
description='A Python wrapper around the OpenWeatherMap web API',
description='A Python wrapper around OpenWeatherMap web APIs',
author='Claudio Sparpaglione (@csparpa)',
author_email='csparpa@gmail.com',
url='http://github.com/csparpa/pyowm',
packages=['pyowm', 'pyowm.abstractions', 'pyowm.alertapi30',
'pyowm.caches', 'pyowm.commons',
'pyowm.exceptions', 'pyowm.utils', 'pyowm.webapi25',
'pyowm.webapi25.cityids', 'pyowm.webapi25.parsers', 'pyowm.webapi25.xsd',
'pyowm.docs', 'pyowm.stationsapi30', 'pyowm.stationsapi30.parsers', 'pyowm.stationsapi30.xsd'],
long_description="""PyOWM is a client Python wrapper library for the
OpenWeatherMap web API. It allows quick and easy consumption of OWM weather
data from Python applications via a simple object model and in a
human-friendly fashion.""",
'pyowm.stationsapi30', 'pyowm.stationsapi30.parsers', 'pyowm.stationsapi30.xsd'],
long_description="""PyOWM is a client Python wrapper library for OpenWeatherMap web APIs. It allows quick and easy
consumption of OWM data from Python applications via a simple object model and in a human-friendly fashion.""",
include_package_data=True,
install_requires=[
'requests>=2.18.2,<3',
Expand Down
File renamed without changes.
319 changes: 319 additions & 0 deletions sphinx/alerts-api-usage-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
# Weather Alert API

You can use the OWM API to create triggers.

Each trigger represents the check if a set of conditions on certain weather parameter values are met over certain geographic areas.

Whenever a condition is met, an alert is fired and stored, and can be retrieved by polling the API.

## OWM website technical reference
- [https://openweathermap.org/triggers](https://openweathermap.org/triggers)
- [http://openweathermap.org/triggers-struct](http://openweathermap.org/triggers-struct)

## A full example first

Hands-on! This is a full example of how to use the Alert API. Check further for details about the involved object types.


```python
from pyowm import OWM
from pyowm.utils import geo
from pyowm.alertapi30.enums import WeatherParametersEnum, OperatorsEnum, AlertChannelsEnum
from pyowm.alertapi30.condition import Condition

# obtain an AlertManager instance
owm = OWM(API_Key='blablabla')
am = owm.alert_manager()

# -- areas --
geom_1 = geo.Point(lon, lat) # available types: Point, MultiPoint, Polygon, MultiPolygon
geom_1.geojson()
'''
{
"type": "Point",
"coordinates":[ lon, lat ]
}
'''
geom_2 = geo.MultiPolygon([[lon1, lat1], [lon2, lat2], [lon3, lat3], [lon1, lat1]]
[[lon7, lat7], [lon8, lat8], [lon9, lat9], [lon7, lat7]])


# -- conditions --
condition_1 = Condition(WeatherParametersEnum.TEMPERATURE,
OperatorsEnum.GREATER_THAN,
313.15) # kelvin
condition_2 = Condition(WeatherParametersEnum.CLOUDS,
OperatorsEnum.EQUAL,
80) # clouds % coverage

# -- triggers --

# create a trigger
trigger = am.create_trigger(start_ts=1234567890, end_ts=1278654300,
conditions=[condition_1, condition_2],
area=[geom_1, geom_2],
alert_channel=AlertChannelsEnum.OWM_API)

# read all triggers
triggers_list = am.get_triggers()

# read one named trigger
trigger_2 = am.get_trigger('trigger_id')

# update a trigger
am.update_trigger(trigger_2)

# delete a trigger
am.delete_trigger(trigger_2)

# -- alerts --

# retrieved from the local parent Trigger obj...
alerts_list = trigger.get_alerts()
alerts_list = trigger.get_alerts_since('2018-01-09T23:07:24Z') # useful for polling alerts
alerts_list = trigger.get_alerts_on(WeatherParametersEnum.TEMPERATURE)
alert = trigger.get_alert('alert_id')

# ...or retrieved from the remote API
alerts_list_alternate = am.get_alerts_for(trigger)
alert_alternate = am.get_alert('alert_id')


# delete all or one alert
am.delete_all_alerts_for(trigger)
am.delete_alert_for(trigger, alert)
```

## Alert API object model

This is the Alert API object model:

- *Trigger*: collection of alerts to be met over specified areas and within a specified time frame according to specified weather params conditions
- *Condition*: rule for matching a weather measuerment with a specified threshold
- *Alert*: whenever a condition is met, an alert is created (or updated) and can be polled to verify when it has been met and what the actual weather param value was.
- *Area*: geographic area over which the trigger is checked
- *AlertChannel*: as OWM plans to add push-oriented alert channels (eg. push notifications), we need to encapsulate this into a specific class

and then you have an *AlertManager* class that you will need to instantiate to operatively interact with the Alert API


### Area

The area describes the geographic boundaries over which a trigger is evaluated. Don't be mislead by the term "area": this
can refer also to a specific geopoint or a set of them, besides - of course - polygons and set of polygons.

Any of the geometry subtypes found in `pyowm.utils.geo` module (point, multipoint, polygon, multipolygon) are fine to use.

Example:

```python
from pyowm.utils import geo
point = geo.Point(20.8, 30.9) # available geometry types: Point, MultiPoint, Polygon, MultiPolygon
point.geojson()
'''
{
"type": "Point",
"coordinates":[ 20.8, 30.9 ]
}
'''
```


Defining complex geometries is sometimes difficult, but in most cases you just need to set triggers upon cities: that's
why we've added a method to the `pyowm.webapi25.cityidregistry.CityIDRegistry` registry that returns the geopoints
that correspond to one or more named cities:

```python
import pyowm
owm = pyowm.OWM('your-API-key')
reg = owm.city_id_registry()
geopoints = reg.geopoints_for('London', country='GB')
```

But still some very spread cities (think of London,GB or Los Angeles,CA) exist and therefore approximating a city to
a single point is not accurate at all: that's why we've added a nice method to get a _squared polygon that is circumscribed
to the circle having a specified geopoint as its centre_. This makes it possible to easily get polygons to cover large
squared areas and you would only need to specify the radius of the circle. Let's do it for London,GB in example:

```python
geopoints = reg.geopoints_for('London', country='GB')
centre = geopoints[0] # the list has only 1 geopoint
square_polygon = centre.bounding_square_polygon(inscribed_circle_radius_km=12) # radius of the inscribed circle in kms (defaults to: 10)
```

Please, notice that if you specify big values for the radius you need to take care about the projection of geographic
coordinates on a proper geoid: this means that if you don't, the polygon will only _approximate_ a square.


Topology is set out as stated by [GeoJSON](https://github.com/frewsxcv/python-geojson)

Moreover, there is a useful factory for Areas: `pyowm.utils.geo.GeometryBuilder.build()`, that you can use to turn a geoJSON standard
dictionary into the corresponding topology type:


```python
from pyowm.utils.geo import GeometryBuilder
the_dict = {
"type": "Point",
"coordinates": [53, 37]
}
geom = GeometryBuilder.build(the_dict)
type(geom) # <pyowm.utils.geo.Point>
```

You can bind multiple `pyowm.utils.geo` geometry types to a Trigger: a list of such geometries is considered to be
the area on which conditions of a Trigger are checked.


### Condition
A condition is a numeric rule to be checked on a named weather variable. Something like:

```
- VARIABLE X IS GREATER THAN AMOUNT_1
- VARIABLE Y IS EQUAL TO AMOUNT_2
- VARIABLE Z IS DIFFERENT FROM AMOUNT_3
```

`GREATER, EQUAL TO, DIFFERENT FROM` are called comparison expressions or operators; `VARIABLE X, Y, Z` are
called target parameters.

Each condition is then specified by:
- target_param: weather parameter to be checked. Can be: `temp, pressure, humidity, wind_speed, wind_direction, clouds`.
- expression: str, operator for the comparison. Can be: `$gt`, $gte, $lt, $lte, $eq, $ne`
- amount: number, the comparison value

Conditions are bound to Triggers, as they are set on Trigger instantiation.

As Conditions can be only set on a limited number of weather variables and can be expressed only through a closed set of
comparison operators, convenient **enumerators** are offered in module `pyowm.alertapi30.enums`:

- `WeatherParametersEnum` --> what weather variable to set this condition on
- `OperatorsEnum` --> what comparison operator to use on the weather parameter

Use enums so that you don't have to remember the syntax of operators and weather params that is specific to the OWM Alert API.
Here is how you use them:

```python
from pyowm.alertapi30 import enums
enums.WeatherParametersEnum.items() # [('TEMPERATURE', 'temp'), ('WIND_SPEED', 'wind_speed'), ... ]
enums.WeatherParametersEnum.TEMPERATURE # 'temp'
enums.WeatherParametersEnum.WIND_SPEED # 'wind_speed'

enums.OperatorsEnum.items() # [('GREATER_THAN', '$gt'), ('NOT_EQUAL', '$ne'), ... ]
enums.OperatorsEnum.GREATER_THAN # '$gt'
enums.OperatorsEnum.NOT_EQUAL # '$ne'

```

Here is an example of conditions:

```python
from pyowm.alertapi30.condition import Condition
from pyowm.alertapi30 import enums

# this condition checks if the temperature is bigger than 313.15 Kelvin degrees
condition = Condition(enums.WeatherParametersEnum.TEMPERATURE,
enums.OperatorsEnum.GREATER_THAN,
313.15)
```

Remember that each Condition is checked by the OWM Alert API on the geographic area that you need to specify!

You can bind multiple `pyowm.alertapi30.condition.Condition` objects to a Trigger: each Alert will be fired when
a specific Condition is met on the area.


### Alert

As said, whenever one or more conditions are met on a certain area, an alert is fired (this means that "the trigger triggers")

If the condition then keeps on being met, more and more alerts will be spawned by the OWM Alert API. You can retrieve
such alerts by polling the OWM API (see below about how to do it).

Each alert is represented by PyOWM as a `pyowm.alertapi30.alert.Alert` instance, having:
- a unique identifier
- timestamp of firing
- a link back to the unique identifier of the parent `pyowm.alertapi30.trigger.Trigger` object instance
- the list of met conditions (each one being a dict containing the `Condition` object and the weather parameter
value that actually made the condition true)
- the geocoordinates where the condition has been met (they belong to the area that had been specified for the Trigger)

Example:

```python
from pyowm.alertapi30.condition import Condition
from pyowm.alertapi30 import enums
from pyowm.alertapi30.alert import Alert

condition = Condition(enums.WeatherParametersEnum.TEMPERATURE,
enums.OperatorsEnum.GREATER_THAN,
356.15)

alert = Alert('alert-id', # alert id
'parent-trigger-id', # parent trigger's id
[{ # list of met conditions
"current_value": 326.4,
"condition": condition
}],
{"lon": 37, "lat": 53}, # coordinates
1481802100000 # fired on
)
```

As you see, you're not meant to create alerts, but PyOWM is supposed to create them for you as they are fired by the
OWM API.


### AlertChannel
Something that OWM envisions, but still does not offer. Possibly, when you will setup a trigger you shall also specify
the channels you want to be notified on: that's why we've added a reference to a list of `AlertChannel` instances
directly on the Trigger objects (the list now only points to the default channel)

A useful enumerator is offered in module `pyowm.alertapi30.enums`: `AlertChannelsEnum` (says what channels should the alerts
delivered to)

As of today, the default `AlertChannel` is: `AlertChannelsEnum.OWM_API_POLLING`, and is the only one available.


### Trigger
As said, each trigger represents the check if a set of conditions on certain weather parameter values are met over
certain geographic areas.

A Trigger is the local proxy for the corresponding entry on the OWM API: Triggers can be operated through
`pyowm.alertapi30.alertmanager.AlertManager` instances.

Each Trigger has these attributes:
- start: timestamp when conditions of the trigger start to be checked by the Alert API
- end: timestamp when conditions of the trigger start to be checked by the Alert API
- alerts: a list of `pyowm.alertapi30.alert.Alert` instances, which are the alerts that the trigger has fired so far
- conditions: a list of `pyowm.alertapi30.condition.Condition` instances
- area: a list of `pyowm.utils.geo.Geometry` instances, representing the geographic area on which the trigger's conditions need to be checked
- alertChannels: list of `pyowm.alertapi30.alert.AlertChannel` objects, representing which channels this trigger is notifying to

**Notes on trigger's time period**: by design, PyOWM will only allow users to specify absolute datetimes for start/end
and will send them to the API using `$exact`

### AlertManager
The OWM main entry point object allows you to get an instance of an `pyowm.alertapi30.alert_manager.AlertManager` object:
use it to interact with the Alert API and create/read/update/delete triggers and read/delete the related alerts.

Here is how to instantiate an `AlertManager`:

```python
from pyowm import OWM

owm = OWM(API_Key='my-API-key')
am = owm.alert_manager()
```

Then you can do some nice things with it:

- create a trigger
- read all of your triggers
- read a named trigger
- modify a named trigger
- delete a named trigger
- read all the alerts fired by a named trigger
- read a named alert
- delete a named alert
- delete all of the alerts for a named trigger

0 comments on commit 336933f

Please sign in to comment.