python-pirateweather 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_pirateweather-1.0.0/LICENSE.txt +13 -0
- python_pirateweather-1.0.0/MANIFEST.in +3 -0
- python_pirateweather-1.0.0/PKG-INFO +200 -0
- python_pirateweather-1.0.0/README.md +188 -0
- python_pirateweather-1.0.0/pirateweather/__init__.py +5 -0
- python_pirateweather-1.0.0/pirateweather/api.py +78 -0
- python_pirateweather-1.0.0/pirateweather/models.py +159 -0
- python_pirateweather-1.0.0/pirateweather/utils.py +13 -0
- python_pirateweather-1.0.0/python_pirateweather.egg-info/PKG-INFO +200 -0
- python_pirateweather-1.0.0/python_pirateweather.egg-info/SOURCES.txt +14 -0
- python_pirateweather-1.0.0/python_pirateweather.egg-info/dependency_links.txt +1 -0
- python_pirateweather-1.0.0/python_pirateweather.egg-info/requires.txt +1 -0
- python_pirateweather-1.0.0/python_pirateweather.egg-info/top_level.txt +1 -0
- python_pirateweather-1.0.0/setup.cfg +4 -0
- python_pirateweather-1.0.0/setup.py +34 -0
- python_pirateweather-1.0.0/tests/test_pirateweather.py +286 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## License (BSD 2-clause)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013, Ze'ev Gilovitz and contributors
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-pirateweather
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A thin Python Wrapper for the Pirate Weather API
|
|
5
|
+
Home-page: https://github.com/cloneofghosts/python-pirate-weather
|
|
6
|
+
Author: cloneofghosts
|
|
7
|
+
License: BSD 2-clause
|
|
8
|
+
Keywords: weather API wrapper pirateweather location
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Requires-Dist: requests==2.32.3
|
|
12
|
+
|
|
13
|
+
# Pirate Weather Wrapper
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
This is a wrapper for the Pirate Weather API. It allows you to get the weather for any location, now, in the past, or future.
|
|
17
|
+
|
|
18
|
+
The Basic Use section covers enough to get you going. I suggest also reading the source if you want to know more about how to use the wrapper or what its doing (it's very simple).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You should use pip to install python-pirateweather.
|
|
24
|
+
|
|
25
|
+
* To install pip install python-pirateweather
|
|
26
|
+
* To remove pip uninstall python-pirateweather
|
|
27
|
+
|
|
28
|
+
Simple!
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- You need an API key to use it (https://pirateweather.net/en/latest/). Don't worry a key is free.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Basic Use
|
|
36
|
+
|
|
37
|
+
Although you don't need to know anything about the Pirate Weather API to use this module, their docs are available at https://pirateweather.net/en/latest/.
|
|
38
|
+
|
|
39
|
+
To use the wrapper:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import pirateweather
|
|
43
|
+
|
|
44
|
+
api_key = "YOUR API KEY"
|
|
45
|
+
lat = -31.967819
|
|
46
|
+
lng = 115.87718
|
|
47
|
+
|
|
48
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The ``load_forecast()`` method has a few optional parameters. Providing your API key, a latitude and longitude are the only required parameters.
|
|
52
|
+
|
|
53
|
+
Use the ``forecast.DataBlockType()`` eg. ``currently()``, ``daily()``, ``hourly()``, ``minutely()`` methods to load the data you are after.
|
|
54
|
+
|
|
55
|
+
These methods return a DataBlock. Except ``currently()`` which returns a DataPoint.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
byHour = forecast.hourly()
|
|
59
|
+
print(byHour.summary)
|
|
60
|
+
print(byHour.icon)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The .data attributes for each DataBlock is a list of DataPoint objects. This is where all the good data is :)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
for hourlyData in byHour.data:
|
|
67
|
+
print(hourlyData.temperature)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## Advanced
|
|
72
|
+
|
|
73
|
+
*function* pirateweather.load_forecast(key, latitude, longitude)
|
|
74
|
+
---------------------------------------------------
|
|
75
|
+
|
|
76
|
+
This makes an API request and returns a **Forecast** object (see below).
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
- **key** - Your API key from https://pirateweather.net/en/latest/.
|
|
80
|
+
- **latitude** - The latitude of the location for the forecast
|
|
81
|
+
- **longitude** - The longitude of the location for the forecast
|
|
82
|
+
- **time** - (optional) A datetime object for the forecast either in the past or future - see How Timezones Work below for the details on how timezones are handled in this library.
|
|
83
|
+
- **lang** - (optional) A string of the desired language. See https://pirateweather.net/en/latest/API/#time-machine-request for supported languages.
|
|
84
|
+
- **units** - (optional) A string of the preferred units of measurement, "us" is the default. "us","ca","uk","si" are also available. See the API Docs (https://pirateweather.net/en/latest/API/#units) for exactly what each unit means.
|
|
85
|
+
- **lazy** - (optional) Defaults to `false`. If `true` the function will request the json data as it is needed. Results in more requests, but maybe a faster response time.
|
|
86
|
+
- **callback** - (optional) Pass a function to be used as a callback. If used, load_forecast() will use an asynchronous HTTP call and **will not return the forecast object directly**, instead it will be passed to the callback function. Make sure it can accept it.
|
|
87
|
+
|
|
88
|
+
----------------------------------------------------
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
*function* pirateweather.manual(url)
|
|
92
|
+
----------------------------------------------------
|
|
93
|
+
This function allows manual creation of the URL for the Pirate Weather API request. This method won't be required often but can be used to take advantage of new or beta features of the API which this wrapper does not support yet. Returns a **Forecast** object (see below).
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
- **url** - The URL which the wrapper will attempt build a forecast from.
|
|
97
|
+
- **callback** - (optional) Pass a function to be used as a callback. If used, an asynchronous HTTP call will be used and ``pirateweather.manual`` **will not return the forecast object directly**, instead it will be passed to the callback function. Make sure it can accept it.
|
|
98
|
+
|
|
99
|
+
----------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
*class* pirateweather.models.Forecast
|
|
103
|
+
------------------------------------
|
|
104
|
+
|
|
105
|
+
The **Forecast** object, it contains both weather data and the HTTP response from Pirate Weather
|
|
106
|
+
|
|
107
|
+
**Attributes**
|
|
108
|
+
- **response**
|
|
109
|
+
- The Response object returned from requests request.get() method. See https://requests.readthedocs.org/en/latest/api/#requests.Response
|
|
110
|
+
- **http_headers**
|
|
111
|
+
- A dictionary of response headers. 'X-Forecast-API-Calls' might be of interest, it contains the number of API calls made by the given API key for the month.
|
|
112
|
+
- **json**
|
|
113
|
+
- A dictionary containing the json data returned from the API call.
|
|
114
|
+
|
|
115
|
+
**Methods**
|
|
116
|
+
- **currently()**
|
|
117
|
+
- Returns a PirateWeatherDataPoint object
|
|
118
|
+
- **minutely()**
|
|
119
|
+
- Returns a PirateWeatherDataBlock object
|
|
120
|
+
- **hourly()**
|
|
121
|
+
- Returns a PirateWeatherDataBlock object
|
|
122
|
+
- **daily()**
|
|
123
|
+
- Returns a PirateWeatherDataBlock object
|
|
124
|
+
- **update()**
|
|
125
|
+
- Refreshes the forecast data by making a new request.
|
|
126
|
+
|
|
127
|
+
----------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
*class* pirateweather.models.PirateWeatherDataBlock
|
|
131
|
+
---------------------------------------------
|
|
132
|
+
|
|
133
|
+
Contains data about a forecast over time.
|
|
134
|
+
|
|
135
|
+
**Attributes** *(descriptions taken from the pirateweather.net website)*
|
|
136
|
+
- **summary**
|
|
137
|
+
- A human-readable text summary of this data block.
|
|
138
|
+
- **icon**
|
|
139
|
+
- A machine-readable text summary of this data block.
|
|
140
|
+
- **data**
|
|
141
|
+
- An array of **PirateWeatherDataPoint** objects (see below), ordered by time, which together describe the weather conditions at the requested location over time.
|
|
142
|
+
|
|
143
|
+
----------------------------------------------------
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
*class* pirateweather.models.PirateWeatherDataPoint
|
|
147
|
+
---------------------------------------------
|
|
148
|
+
|
|
149
|
+
Contains data about a forecast at a particular time.
|
|
150
|
+
|
|
151
|
+
Data points have many attributes, but **not all of them are always available**. Some commonly used ones are:
|
|
152
|
+
|
|
153
|
+
**Attributes** *(descriptions taken from the pirateweather.net website)*
|
|
154
|
+
- **summary**
|
|
155
|
+
- A human-readable text summary of this data block.
|
|
156
|
+
- **icon**
|
|
157
|
+
- A machine-readable text summary of this data block.
|
|
158
|
+
- **time**
|
|
159
|
+
- The time at which this data point occurs.
|
|
160
|
+
- **temperature**
|
|
161
|
+
- (not defined on daily data points): A numerical value representing the temperature at the given time.
|
|
162
|
+
- **precipProbability**
|
|
163
|
+
- A numerical value between 0 and 1 (inclusive) representing the probability of precipitation occurring at the given time.
|
|
164
|
+
|
|
165
|
+
For a full list of PirateWeatherDataPoint attributes and attribute descriptions, take a look at the Pirate Weather data point documentation (https://pirateweather.net/en/latest/API/#data-point)
|
|
166
|
+
|
|
167
|
+
----------------------------------------------------
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
How Timezones Work
|
|
171
|
+
------------------
|
|
172
|
+
Requests with a naive datetime (no time zone specified) will correspond to the supplied time in the requesting location. If a timezone aware datetime object is supplied, the supplied time will be in the associated timezone.
|
|
173
|
+
|
|
174
|
+
Returned times eg the time parameter on the currently DataPoint are always in UTC time even if making a request with a timezone. If you want to manually convert to the locations local time, you can use the `offset` and `timezone` attributes of the forecast object.
|
|
175
|
+
|
|
176
|
+
Typically, would would want to do something like this:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
# Amsterdam
|
|
180
|
+
lat = 52.370235
|
|
181
|
+
lng = 4.903549
|
|
182
|
+
current_time = datetime(2015, 2, 27, 6, 0, 0)
|
|
183
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng, time=current_time)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Be caerful, things can get confusing when doing something like the below. Given that I'm looking up the weather in Amsterdam (+2) while I'm in Perth, Australia (+8).
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# Amsterdam
|
|
190
|
+
lat = 52.370235
|
|
191
|
+
lng = 4.903549
|
|
192
|
+
|
|
193
|
+
current_time = datetime.datetime.now()
|
|
194
|
+
|
|
195
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng, time=current_time)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The result is actually a request for the weather in the future in Amsterdam (by 6 hours). In addition, since all returned times are in UTC, it will report a time two hours behind the *local* time in Amsterdam.
|
|
199
|
+
|
|
200
|
+
If you're doing lots of queries in the past/future in different locations, the best approach is to consistently use UTC time. Keep in mind `datetime.datetime.utcnow()` is **still a naive datetime**. To use proper timezone aware datetime objects you will need to use a library like `pytz <http://pytz.sourceforge.net/>`_
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Pirate Weather Wrapper
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This is a wrapper for the Pirate Weather API. It allows you to get the weather for any location, now, in the past, or future.
|
|
5
|
+
|
|
6
|
+
The Basic Use section covers enough to get you going. I suggest also reading the source if you want to know more about how to use the wrapper or what its doing (it's very simple).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
You should use pip to install python-pirateweather.
|
|
12
|
+
|
|
13
|
+
* To install pip install python-pirateweather
|
|
14
|
+
* To remove pip uninstall python-pirateweather
|
|
15
|
+
|
|
16
|
+
Simple!
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- You need an API key to use it (https://pirateweather.net/en/latest/). Don't worry a key is free.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Basic Use
|
|
24
|
+
|
|
25
|
+
Although you don't need to know anything about the Pirate Weather API to use this module, their docs are available at https://pirateweather.net/en/latest/.
|
|
26
|
+
|
|
27
|
+
To use the wrapper:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import pirateweather
|
|
31
|
+
|
|
32
|
+
api_key = "YOUR API KEY"
|
|
33
|
+
lat = -31.967819
|
|
34
|
+
lng = 115.87718
|
|
35
|
+
|
|
36
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The ``load_forecast()`` method has a few optional parameters. Providing your API key, a latitude and longitude are the only required parameters.
|
|
40
|
+
|
|
41
|
+
Use the ``forecast.DataBlockType()`` eg. ``currently()``, ``daily()``, ``hourly()``, ``minutely()`` methods to load the data you are after.
|
|
42
|
+
|
|
43
|
+
These methods return a DataBlock. Except ``currently()`` which returns a DataPoint.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
byHour = forecast.hourly()
|
|
47
|
+
print(byHour.summary)
|
|
48
|
+
print(byHour.icon)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The .data attributes for each DataBlock is a list of DataPoint objects. This is where all the good data is :)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
for hourlyData in byHour.data:
|
|
55
|
+
print(hourlyData.temperature)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Advanced
|
|
60
|
+
|
|
61
|
+
*function* pirateweather.load_forecast(key, latitude, longitude)
|
|
62
|
+
---------------------------------------------------
|
|
63
|
+
|
|
64
|
+
This makes an API request and returns a **Forecast** object (see below).
|
|
65
|
+
|
|
66
|
+
Parameters:
|
|
67
|
+
- **key** - Your API key from https://pirateweather.net/en/latest/.
|
|
68
|
+
- **latitude** - The latitude of the location for the forecast
|
|
69
|
+
- **longitude** - The longitude of the location for the forecast
|
|
70
|
+
- **time** - (optional) A datetime object for the forecast either in the past or future - see How Timezones Work below for the details on how timezones are handled in this library.
|
|
71
|
+
- **lang** - (optional) A string of the desired language. See https://pirateweather.net/en/latest/API/#time-machine-request for supported languages.
|
|
72
|
+
- **units** - (optional) A string of the preferred units of measurement, "us" is the default. "us","ca","uk","si" are also available. See the API Docs (https://pirateweather.net/en/latest/API/#units) for exactly what each unit means.
|
|
73
|
+
- **lazy** - (optional) Defaults to `false`. If `true` the function will request the json data as it is needed. Results in more requests, but maybe a faster response time.
|
|
74
|
+
- **callback** - (optional) Pass a function to be used as a callback. If used, load_forecast() will use an asynchronous HTTP call and **will not return the forecast object directly**, instead it will be passed to the callback function. Make sure it can accept it.
|
|
75
|
+
|
|
76
|
+
----------------------------------------------------
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
*function* pirateweather.manual(url)
|
|
80
|
+
----------------------------------------------------
|
|
81
|
+
This function allows manual creation of the URL for the Pirate Weather API request. This method won't be required often but can be used to take advantage of new or beta features of the API which this wrapper does not support yet. Returns a **Forecast** object (see below).
|
|
82
|
+
|
|
83
|
+
Parameters:
|
|
84
|
+
- **url** - The URL which the wrapper will attempt build a forecast from.
|
|
85
|
+
- **callback** - (optional) Pass a function to be used as a callback. If used, an asynchronous HTTP call will be used and ``pirateweather.manual`` **will not return the forecast object directly**, instead it will be passed to the callback function. Make sure it can accept it.
|
|
86
|
+
|
|
87
|
+
----------------------------------------------------
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
*class* pirateweather.models.Forecast
|
|
91
|
+
------------------------------------
|
|
92
|
+
|
|
93
|
+
The **Forecast** object, it contains both weather data and the HTTP response from Pirate Weather
|
|
94
|
+
|
|
95
|
+
**Attributes**
|
|
96
|
+
- **response**
|
|
97
|
+
- The Response object returned from requests request.get() method. See https://requests.readthedocs.org/en/latest/api/#requests.Response
|
|
98
|
+
- **http_headers**
|
|
99
|
+
- A dictionary of response headers. 'X-Forecast-API-Calls' might be of interest, it contains the number of API calls made by the given API key for the month.
|
|
100
|
+
- **json**
|
|
101
|
+
- A dictionary containing the json data returned from the API call.
|
|
102
|
+
|
|
103
|
+
**Methods**
|
|
104
|
+
- **currently()**
|
|
105
|
+
- Returns a PirateWeatherDataPoint object
|
|
106
|
+
- **minutely()**
|
|
107
|
+
- Returns a PirateWeatherDataBlock object
|
|
108
|
+
- **hourly()**
|
|
109
|
+
- Returns a PirateWeatherDataBlock object
|
|
110
|
+
- **daily()**
|
|
111
|
+
- Returns a PirateWeatherDataBlock object
|
|
112
|
+
- **update()**
|
|
113
|
+
- Refreshes the forecast data by making a new request.
|
|
114
|
+
|
|
115
|
+
----------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
*class* pirateweather.models.PirateWeatherDataBlock
|
|
119
|
+
---------------------------------------------
|
|
120
|
+
|
|
121
|
+
Contains data about a forecast over time.
|
|
122
|
+
|
|
123
|
+
**Attributes** *(descriptions taken from the pirateweather.net website)*
|
|
124
|
+
- **summary**
|
|
125
|
+
- A human-readable text summary of this data block.
|
|
126
|
+
- **icon**
|
|
127
|
+
- A machine-readable text summary of this data block.
|
|
128
|
+
- **data**
|
|
129
|
+
- An array of **PirateWeatherDataPoint** objects (see below), ordered by time, which together describe the weather conditions at the requested location over time.
|
|
130
|
+
|
|
131
|
+
----------------------------------------------------
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
*class* pirateweather.models.PirateWeatherDataPoint
|
|
135
|
+
---------------------------------------------
|
|
136
|
+
|
|
137
|
+
Contains data about a forecast at a particular time.
|
|
138
|
+
|
|
139
|
+
Data points have many attributes, but **not all of them are always available**. Some commonly used ones are:
|
|
140
|
+
|
|
141
|
+
**Attributes** *(descriptions taken from the pirateweather.net website)*
|
|
142
|
+
- **summary**
|
|
143
|
+
- A human-readable text summary of this data block.
|
|
144
|
+
- **icon**
|
|
145
|
+
- A machine-readable text summary of this data block.
|
|
146
|
+
- **time**
|
|
147
|
+
- The time at which this data point occurs.
|
|
148
|
+
- **temperature**
|
|
149
|
+
- (not defined on daily data points): A numerical value representing the temperature at the given time.
|
|
150
|
+
- **precipProbability**
|
|
151
|
+
- A numerical value between 0 and 1 (inclusive) representing the probability of precipitation occurring at the given time.
|
|
152
|
+
|
|
153
|
+
For a full list of PirateWeatherDataPoint attributes and attribute descriptions, take a look at the Pirate Weather data point documentation (https://pirateweather.net/en/latest/API/#data-point)
|
|
154
|
+
|
|
155
|
+
----------------------------------------------------
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
How Timezones Work
|
|
159
|
+
------------------
|
|
160
|
+
Requests with a naive datetime (no time zone specified) will correspond to the supplied time in the requesting location. If a timezone aware datetime object is supplied, the supplied time will be in the associated timezone.
|
|
161
|
+
|
|
162
|
+
Returned times eg the time parameter on the currently DataPoint are always in UTC time even if making a request with a timezone. If you want to manually convert to the locations local time, you can use the `offset` and `timezone` attributes of the forecast object.
|
|
163
|
+
|
|
164
|
+
Typically, would would want to do something like this:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# Amsterdam
|
|
168
|
+
lat = 52.370235
|
|
169
|
+
lng = 4.903549
|
|
170
|
+
current_time = datetime(2015, 2, 27, 6, 0, 0)
|
|
171
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng, time=current_time)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Be caerful, things can get confusing when doing something like the below. Given that I'm looking up the weather in Amsterdam (+2) while I'm in Perth, Australia (+8).
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
# Amsterdam
|
|
178
|
+
lat = 52.370235
|
|
179
|
+
lng = 4.903549
|
|
180
|
+
|
|
181
|
+
current_time = datetime.datetime.now()
|
|
182
|
+
|
|
183
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng, time=current_time)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The result is actually a request for the weather in the future in Amsterdam (by 6 hours). In addition, since all returned times are in UTC, it will report a time two hours behind the *local* time in Amsterdam.
|
|
187
|
+
|
|
188
|
+
If you're doing lots of queries in the past/future in different locations, the best approach is to consistently use UTC time. Keep in mind `datetime.datetime.utcnow()` is **still a naive datetime**. To use proper timezone aware datetime objects you will need to use a library like `pytz <http://pytz.sourceforge.net/>`_
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Fetches data from the Pirate Weather API."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pirateweather.models import Forecast
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_forecast(
|
|
11
|
+
key, lat, lng, time=None, units="us", lang="en", lazy=False, callback=None
|
|
12
|
+
):
|
|
13
|
+
"""Build the request url and loads some or all of the needed json depending on lazy is True.
|
|
14
|
+
|
|
15
|
+
inLat: The latitude of the forecast
|
|
16
|
+
inLong: The longitude of the forecast
|
|
17
|
+
time: A datetime.datetime object representing the desired time of
|
|
18
|
+
the forecast. If no timezone is present, the API assumes local
|
|
19
|
+
time at the provided latitude and longitude.
|
|
20
|
+
units: A string of the preferred units of measurement, "us" id
|
|
21
|
+
default. also ca,uk,si is available
|
|
22
|
+
lang: Return summary properties in the desired language
|
|
23
|
+
lazy: Defaults to false. The function will only request the json
|
|
24
|
+
data as it is needed. Results in more requests, but
|
|
25
|
+
probably a faster response time (I haven't checked)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
if time is None:
|
|
29
|
+
url = (
|
|
30
|
+
f"https://api.pirateweather.net/forecast/{key}/{lat},{lng}"
|
|
31
|
+
f"?units={units}&lang={lang}"
|
|
32
|
+
)
|
|
33
|
+
else:
|
|
34
|
+
url_time = time.replace(
|
|
35
|
+
microsecond=0
|
|
36
|
+
).isoformat() # API returns 400 for microseconds
|
|
37
|
+
url = (
|
|
38
|
+
f"https://timemachine.pirateweather.net/forecast/{key}/{lat},{lng},{url_time}"
|
|
39
|
+
f"?units={units}&lang={lang}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if lazy is True:
|
|
43
|
+
baseURL = "{}&exclude={}".format(
|
|
44
|
+
url,
|
|
45
|
+
"minutely,currently,hourly," "daily,alerts,flags",
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
baseURL = url
|
|
49
|
+
|
|
50
|
+
return manual(baseURL, callback=callback)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def manual(requestURL, callback=None):
|
|
54
|
+
"""Manually construct the URL for an API call used by load_forecast OR by users."""
|
|
55
|
+
|
|
56
|
+
if callback is None:
|
|
57
|
+
return get_forecast(requestURL)
|
|
58
|
+
thread = threading.Thread(target=load_async, args=(requestURL, callback))
|
|
59
|
+
thread.start()
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_forecast(requestURL):
|
|
64
|
+
"""Get the forecast from the Pirate Weather API."""
|
|
65
|
+
|
|
66
|
+
pirateweather_reponse = requests.get(requestURL, timeout=60)
|
|
67
|
+
pirateweather_reponse.raise_for_status()
|
|
68
|
+
|
|
69
|
+
json = pirateweather_reponse.json()
|
|
70
|
+
headers = pirateweather_reponse.headers
|
|
71
|
+
|
|
72
|
+
return Forecast(json, pirateweather_reponse, headers)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_async(url, callback):
|
|
76
|
+
"""Get the forecast from the Pirate Weather API asynchronously."""
|
|
77
|
+
|
|
78
|
+
callback(get_forecast(url))
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Models used in the Pirate Weather library."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pirateweather.utils import PropertyUnavailable, UnicodeMixin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Forecast(UnicodeMixin):
|
|
11
|
+
"""Represent the forecast data and provide methods to access weather blocks."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, data, response, headers):
|
|
14
|
+
"""Initialize the Forecast with data, HTTP response, and headers."""
|
|
15
|
+
self.response = response
|
|
16
|
+
self.http_headers = headers
|
|
17
|
+
self.json = data
|
|
18
|
+
|
|
19
|
+
self._alerts = []
|
|
20
|
+
for alertJSON in self.json.get("alerts", []):
|
|
21
|
+
self._alerts.append(Alert(alertJSON))
|
|
22
|
+
|
|
23
|
+
def update(self):
|
|
24
|
+
"""Update the forecast data by making a new request to the same URL."""
|
|
25
|
+
r = requests.get(self.response.url)
|
|
26
|
+
self.json = r.json()
|
|
27
|
+
self.response = r
|
|
28
|
+
|
|
29
|
+
def currently(self):
|
|
30
|
+
"""Return the current weather data block."""
|
|
31
|
+
return self._pirateweather_data("currently")
|
|
32
|
+
|
|
33
|
+
def minutely(self):
|
|
34
|
+
"""Return the minutely weather data block."""
|
|
35
|
+
return self._pirateweather_data("minutely")
|
|
36
|
+
|
|
37
|
+
def hourly(self):
|
|
38
|
+
"""Return the hourly weather data block."""
|
|
39
|
+
return self._pirateweather_data("hourly")
|
|
40
|
+
|
|
41
|
+
def daily(self):
|
|
42
|
+
"""Return the daily weather data block."""
|
|
43
|
+
return self._pirateweather_data("daily")
|
|
44
|
+
|
|
45
|
+
def offset(self):
|
|
46
|
+
"""Return the time zone offset for the forecast location."""
|
|
47
|
+
return self.json["offset"]
|
|
48
|
+
|
|
49
|
+
def alerts(self):
|
|
50
|
+
"""Return the list of alerts issued for this forecast."""
|
|
51
|
+
return self._alerts
|
|
52
|
+
|
|
53
|
+
def _pirateweather_data(self, key):
|
|
54
|
+
"""Fetch and return specific weather data (currently, minutely, hourly, daily)."""
|
|
55
|
+
keys = ["minutely", "currently", "hourly", "daily"]
|
|
56
|
+
try:
|
|
57
|
+
if key not in self.json:
|
|
58
|
+
keys.remove(key)
|
|
59
|
+
url = "{}&exclude={}{}".format(
|
|
60
|
+
self.response.url.split("&")[0],
|
|
61
|
+
",".join(keys),
|
|
62
|
+
",alerts,flags",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
response = requests.get(url).json()
|
|
66
|
+
self.json[key] = response[key]
|
|
67
|
+
|
|
68
|
+
if key == "currently":
|
|
69
|
+
return PirateWeatherDataPoint(self.json[key])
|
|
70
|
+
return PirateWeatherDataBlock(self.json[key])
|
|
71
|
+
except KeyError:
|
|
72
|
+
if key == "currently":
|
|
73
|
+
return PirateWeatherDataPoint()
|
|
74
|
+
return PirateWeatherDataBlock()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class PirateWeatherDataBlock(UnicodeMixin):
|
|
78
|
+
"""Represent a block of weather data such as minutely, hourly, or daily summaries."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, d=None):
|
|
81
|
+
"""Initialize the data block with summary and icon information."""
|
|
82
|
+
d = d or {}
|
|
83
|
+
self.summary = d.get("summary")
|
|
84
|
+
self.icon = d.get("icon")
|
|
85
|
+
self.data = [
|
|
86
|
+
PirateWeatherDataPoint(datapoint) for datapoint in d.get("data", [])
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
def __unicode__(self):
|
|
90
|
+
"""Return a string representation of the data block."""
|
|
91
|
+
return (
|
|
92
|
+
"<PirateWeatherDataBlock instance: "
|
|
93
|
+
"%s with %d PirateWeatherDataPoints>"
|
|
94
|
+
% (
|
|
95
|
+
self.summary,
|
|
96
|
+
len(self.data),
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PirateWeatherDataPoint(UnicodeMixin):
|
|
102
|
+
"""Represent a single data point in a weather forecast, such as an hourly or daily data point."""
|
|
103
|
+
|
|
104
|
+
def __init__(self, d={}):
|
|
105
|
+
"""Initialize the data point with timestamp and weather information."""
|
|
106
|
+
self.d = d
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
self.time = datetime.datetime.fromtimestamp(int(d["time"]))
|
|
110
|
+
self.utime = d["time"]
|
|
111
|
+
except KeyError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
sr_time = int(d["sunriseTime"])
|
|
116
|
+
self.sunriseTime = datetime.datetime.fromtimestamp(sr_time)
|
|
117
|
+
except KeyError:
|
|
118
|
+
self.sunriseTime = None
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
ss_time = int(d["sunsetTime"])
|
|
122
|
+
self.sunsetTime = datetime.datetime.fromtimestamp(ss_time)
|
|
123
|
+
except KeyError:
|
|
124
|
+
self.sunsetTime = None
|
|
125
|
+
|
|
126
|
+
def __getattr__(self, name):
|
|
127
|
+
"""Return the weather property dynamically or raise PropertyUnavailable if missing."""
|
|
128
|
+
try:
|
|
129
|
+
return self.d[name]
|
|
130
|
+
except KeyError as err:
|
|
131
|
+
raise PropertyUnavailable(
|
|
132
|
+
f"Property '{name}' is not valid"
|
|
133
|
+
" or is not available for this forecast"
|
|
134
|
+
) from err
|
|
135
|
+
|
|
136
|
+
def __unicode__(self):
|
|
137
|
+
"""Return a string representation of the data point."""
|
|
138
|
+
return "<PirateWeatherDataPoint instance: " f"{self.summary} at {self.time}>"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Alert(UnicodeMixin):
|
|
142
|
+
"""Represent a weather alert, such as a storm warning or flood alert."""
|
|
143
|
+
|
|
144
|
+
def __init__(self, json):
|
|
145
|
+
"""Initialize the alert with the raw JSON data."""
|
|
146
|
+
self.json = json
|
|
147
|
+
|
|
148
|
+
def __getattr__(self, name):
|
|
149
|
+
"""Return the alert property dynamically or raise PropertyUnavailable if missing."""
|
|
150
|
+
try:
|
|
151
|
+
return self.json[name]
|
|
152
|
+
except KeyError as err:
|
|
153
|
+
raise PropertyUnavailable(
|
|
154
|
+
f"Property '{name}' is not valid" " or is not available for this alert"
|
|
155
|
+
) from err
|
|
156
|
+
|
|
157
|
+
def __unicode__(self):
|
|
158
|
+
"""Return a string representation of the alert."""
|
|
159
|
+
return f"<Alert instance: {self.title} at {self.time}>"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Utilities for the Pirate Weather library."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UnicodeMixin:
|
|
5
|
+
"""Mixin class to handle defining the proper __str__/__unicode__ methods in Python 3."""
|
|
6
|
+
|
|
7
|
+
def __str__(self):
|
|
8
|
+
"""Return the unicode representation of the object for Python 3 compatibility."""
|
|
9
|
+
return self.__unicode__()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PropertyUnavailable(AttributeError):
|
|
13
|
+
"""Raise when a requested property is unavailable in the forecast data."""
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-pirateweather
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A thin Python Wrapper for the Pirate Weather API
|
|
5
|
+
Home-page: https://github.com/cloneofghosts/python-pirate-weather
|
|
6
|
+
Author: cloneofghosts
|
|
7
|
+
License: BSD 2-clause
|
|
8
|
+
Keywords: weather API wrapper pirateweather location
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Requires-Dist: requests==2.32.3
|
|
12
|
+
|
|
13
|
+
# Pirate Weather Wrapper
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
This is a wrapper for the Pirate Weather API. It allows you to get the weather for any location, now, in the past, or future.
|
|
17
|
+
|
|
18
|
+
The Basic Use section covers enough to get you going. I suggest also reading the source if you want to know more about how to use the wrapper or what its doing (it's very simple).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You should use pip to install python-pirateweather.
|
|
24
|
+
|
|
25
|
+
* To install pip install python-pirateweather
|
|
26
|
+
* To remove pip uninstall python-pirateweather
|
|
27
|
+
|
|
28
|
+
Simple!
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- You need an API key to use it (https://pirateweather.net/en/latest/). Don't worry a key is free.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Basic Use
|
|
36
|
+
|
|
37
|
+
Although you don't need to know anything about the Pirate Weather API to use this module, their docs are available at https://pirateweather.net/en/latest/.
|
|
38
|
+
|
|
39
|
+
To use the wrapper:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import pirateweather
|
|
43
|
+
|
|
44
|
+
api_key = "YOUR API KEY"
|
|
45
|
+
lat = -31.967819
|
|
46
|
+
lng = 115.87718
|
|
47
|
+
|
|
48
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The ``load_forecast()`` method has a few optional parameters. Providing your API key, a latitude and longitude are the only required parameters.
|
|
52
|
+
|
|
53
|
+
Use the ``forecast.DataBlockType()`` eg. ``currently()``, ``daily()``, ``hourly()``, ``minutely()`` methods to load the data you are after.
|
|
54
|
+
|
|
55
|
+
These methods return a DataBlock. Except ``currently()`` which returns a DataPoint.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
byHour = forecast.hourly()
|
|
59
|
+
print(byHour.summary)
|
|
60
|
+
print(byHour.icon)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The .data attributes for each DataBlock is a list of DataPoint objects. This is where all the good data is :)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
for hourlyData in byHour.data:
|
|
67
|
+
print(hourlyData.temperature)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## Advanced
|
|
72
|
+
|
|
73
|
+
*function* pirateweather.load_forecast(key, latitude, longitude)
|
|
74
|
+
---------------------------------------------------
|
|
75
|
+
|
|
76
|
+
This makes an API request and returns a **Forecast** object (see below).
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
- **key** - Your API key from https://pirateweather.net/en/latest/.
|
|
80
|
+
- **latitude** - The latitude of the location for the forecast
|
|
81
|
+
- **longitude** - The longitude of the location for the forecast
|
|
82
|
+
- **time** - (optional) A datetime object for the forecast either in the past or future - see How Timezones Work below for the details on how timezones are handled in this library.
|
|
83
|
+
- **lang** - (optional) A string of the desired language. See https://pirateweather.net/en/latest/API/#time-machine-request for supported languages.
|
|
84
|
+
- **units** - (optional) A string of the preferred units of measurement, "us" is the default. "us","ca","uk","si" are also available. See the API Docs (https://pirateweather.net/en/latest/API/#units) for exactly what each unit means.
|
|
85
|
+
- **lazy** - (optional) Defaults to `false`. If `true` the function will request the json data as it is needed. Results in more requests, but maybe a faster response time.
|
|
86
|
+
- **callback** - (optional) Pass a function to be used as a callback. If used, load_forecast() will use an asynchronous HTTP call and **will not return the forecast object directly**, instead it will be passed to the callback function. Make sure it can accept it.
|
|
87
|
+
|
|
88
|
+
----------------------------------------------------
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
*function* pirateweather.manual(url)
|
|
92
|
+
----------------------------------------------------
|
|
93
|
+
This function allows manual creation of the URL for the Pirate Weather API request. This method won't be required often but can be used to take advantage of new or beta features of the API which this wrapper does not support yet. Returns a **Forecast** object (see below).
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
- **url** - The URL which the wrapper will attempt build a forecast from.
|
|
97
|
+
- **callback** - (optional) Pass a function to be used as a callback. If used, an asynchronous HTTP call will be used and ``pirateweather.manual`` **will not return the forecast object directly**, instead it will be passed to the callback function. Make sure it can accept it.
|
|
98
|
+
|
|
99
|
+
----------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
*class* pirateweather.models.Forecast
|
|
103
|
+
------------------------------------
|
|
104
|
+
|
|
105
|
+
The **Forecast** object, it contains both weather data and the HTTP response from Pirate Weather
|
|
106
|
+
|
|
107
|
+
**Attributes**
|
|
108
|
+
- **response**
|
|
109
|
+
- The Response object returned from requests request.get() method. See https://requests.readthedocs.org/en/latest/api/#requests.Response
|
|
110
|
+
- **http_headers**
|
|
111
|
+
- A dictionary of response headers. 'X-Forecast-API-Calls' might be of interest, it contains the number of API calls made by the given API key for the month.
|
|
112
|
+
- **json**
|
|
113
|
+
- A dictionary containing the json data returned from the API call.
|
|
114
|
+
|
|
115
|
+
**Methods**
|
|
116
|
+
- **currently()**
|
|
117
|
+
- Returns a PirateWeatherDataPoint object
|
|
118
|
+
- **minutely()**
|
|
119
|
+
- Returns a PirateWeatherDataBlock object
|
|
120
|
+
- **hourly()**
|
|
121
|
+
- Returns a PirateWeatherDataBlock object
|
|
122
|
+
- **daily()**
|
|
123
|
+
- Returns a PirateWeatherDataBlock object
|
|
124
|
+
- **update()**
|
|
125
|
+
- Refreshes the forecast data by making a new request.
|
|
126
|
+
|
|
127
|
+
----------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
*class* pirateweather.models.PirateWeatherDataBlock
|
|
131
|
+
---------------------------------------------
|
|
132
|
+
|
|
133
|
+
Contains data about a forecast over time.
|
|
134
|
+
|
|
135
|
+
**Attributes** *(descriptions taken from the pirateweather.net website)*
|
|
136
|
+
- **summary**
|
|
137
|
+
- A human-readable text summary of this data block.
|
|
138
|
+
- **icon**
|
|
139
|
+
- A machine-readable text summary of this data block.
|
|
140
|
+
- **data**
|
|
141
|
+
- An array of **PirateWeatherDataPoint** objects (see below), ordered by time, which together describe the weather conditions at the requested location over time.
|
|
142
|
+
|
|
143
|
+
----------------------------------------------------
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
*class* pirateweather.models.PirateWeatherDataPoint
|
|
147
|
+
---------------------------------------------
|
|
148
|
+
|
|
149
|
+
Contains data about a forecast at a particular time.
|
|
150
|
+
|
|
151
|
+
Data points have many attributes, but **not all of them are always available**. Some commonly used ones are:
|
|
152
|
+
|
|
153
|
+
**Attributes** *(descriptions taken from the pirateweather.net website)*
|
|
154
|
+
- **summary**
|
|
155
|
+
- A human-readable text summary of this data block.
|
|
156
|
+
- **icon**
|
|
157
|
+
- A machine-readable text summary of this data block.
|
|
158
|
+
- **time**
|
|
159
|
+
- The time at which this data point occurs.
|
|
160
|
+
- **temperature**
|
|
161
|
+
- (not defined on daily data points): A numerical value representing the temperature at the given time.
|
|
162
|
+
- **precipProbability**
|
|
163
|
+
- A numerical value between 0 and 1 (inclusive) representing the probability of precipitation occurring at the given time.
|
|
164
|
+
|
|
165
|
+
For a full list of PirateWeatherDataPoint attributes and attribute descriptions, take a look at the Pirate Weather data point documentation (https://pirateweather.net/en/latest/API/#data-point)
|
|
166
|
+
|
|
167
|
+
----------------------------------------------------
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
How Timezones Work
|
|
171
|
+
------------------
|
|
172
|
+
Requests with a naive datetime (no time zone specified) will correspond to the supplied time in the requesting location. If a timezone aware datetime object is supplied, the supplied time will be in the associated timezone.
|
|
173
|
+
|
|
174
|
+
Returned times eg the time parameter on the currently DataPoint are always in UTC time even if making a request with a timezone. If you want to manually convert to the locations local time, you can use the `offset` and `timezone` attributes of the forecast object.
|
|
175
|
+
|
|
176
|
+
Typically, would would want to do something like this:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
# Amsterdam
|
|
180
|
+
lat = 52.370235
|
|
181
|
+
lng = 4.903549
|
|
182
|
+
current_time = datetime(2015, 2, 27, 6, 0, 0)
|
|
183
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng, time=current_time)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Be caerful, things can get confusing when doing something like the below. Given that I'm looking up the weather in Amsterdam (+2) while I'm in Perth, Australia (+8).
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# Amsterdam
|
|
190
|
+
lat = 52.370235
|
|
191
|
+
lng = 4.903549
|
|
192
|
+
|
|
193
|
+
current_time = datetime.datetime.now()
|
|
194
|
+
|
|
195
|
+
forecast = pirateweather.load_forecast(api_key, lat, lng, time=current_time)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The result is actually a request for the weather in the future in Amsterdam (by 6 hours). In addition, since all returned times are in UTC, it will report a time two hours behind the *local* time in Amsterdam.
|
|
199
|
+
|
|
200
|
+
If you're doing lots of queries in the past/future in different locations, the best approach is to consistently use UTC time. Keep in mind `datetime.datetime.utcnow()` is **still a naive datetime**. To use proper timezone aware datetime objects you will need to use a library like `pytz <http://pytz.sourceforge.net/>`_
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
setup.py
|
|
5
|
+
pirateweather/__init__.py
|
|
6
|
+
pirateweather/api.py
|
|
7
|
+
pirateweather/models.py
|
|
8
|
+
pirateweather/utils.py
|
|
9
|
+
python_pirateweather.egg-info/PKG-INFO
|
|
10
|
+
python_pirateweather.egg-info/SOURCES.txt
|
|
11
|
+
python_pirateweather.egg-info/dependency_links.txt
|
|
12
|
+
python_pirateweather.egg-info/requires.txt
|
|
13
|
+
python_pirateweather.egg-info/top_level.txt
|
|
14
|
+
tests/test_pirateweather.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests==2.32.3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pirateweather
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Set up the Pirate Weather library."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from setuptools import setup
|
|
8
|
+
except ImportError:
|
|
9
|
+
from distutils.core import setup
|
|
10
|
+
|
|
11
|
+
if sys.argv[-1] == "publish":
|
|
12
|
+
os.system("python setup.py sdist upload")
|
|
13
|
+
sys.exit()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read(fname):
|
|
17
|
+
"""Read the specified file."""
|
|
18
|
+
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
setup(
|
|
22
|
+
name="python-pirateweather",
|
|
23
|
+
version="1.0.0",
|
|
24
|
+
author="cloneofghosts",
|
|
25
|
+
description=("A thin Python Wrapper for the Pirate Weather API"),
|
|
26
|
+
license="BSD 2-clause",
|
|
27
|
+
keywords="weather API wrapper pirateweather location",
|
|
28
|
+
url="https://github.com/cloneofghosts/python-pirate-weather",
|
|
29
|
+
packages=["pirateweather"],
|
|
30
|
+
package_data={"pirateweather": ["LICENSE.txt", "README.md"]},
|
|
31
|
+
long_description=open("README.md").read(),
|
|
32
|
+
long_description_content_type="text/markdown",
|
|
33
|
+
install_requires=["requests==2.32.3"],
|
|
34
|
+
)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Test the Pirate Weather library."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import unittest
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
import requests
|
|
9
|
+
import responses
|
|
10
|
+
from nose.tools import raises
|
|
11
|
+
|
|
12
|
+
import pirateweather
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EndToEnd(unittest.TestCase):
|
|
16
|
+
"""Test the Pirate Weather library."""
|
|
17
|
+
|
|
18
|
+
def setUp(self):
|
|
19
|
+
"""Set up the API key, location and time for the tests."""
|
|
20
|
+
|
|
21
|
+
self.api_key = os.environ.get("PIRATEWEATHER_API_KEY")
|
|
22
|
+
|
|
23
|
+
self.lat = 52.370235
|
|
24
|
+
self.lng = 4.903549
|
|
25
|
+
|
|
26
|
+
self.time = datetime(2015, 2, 27, 6, 0, 0)
|
|
27
|
+
|
|
28
|
+
def test_with_time(self):
|
|
29
|
+
"""Test querying the TimeMachine API endpoint."""
|
|
30
|
+
|
|
31
|
+
forecast = pirateweather.load_forecast(
|
|
32
|
+
self.api_key, self.lat, self.lng, time=self.time
|
|
33
|
+
)
|
|
34
|
+
assert forecast.response.status_code == 200
|
|
35
|
+
|
|
36
|
+
def test_with_language(self):
|
|
37
|
+
"""Test querying the API endpoint with a set language."""
|
|
38
|
+
|
|
39
|
+
forecast = pirateweather.load_forecast(
|
|
40
|
+
self.api_key, self.lat, self.lng, time=self.time, lang="de"
|
|
41
|
+
)
|
|
42
|
+
# Unfortunately the API doesn't return anything which
|
|
43
|
+
# states the language of the response. This is the best we can do...
|
|
44
|
+
assert forecast.response.status_code == 200
|
|
45
|
+
assert forecast.response.url.find("lang=de") >= 0
|
|
46
|
+
|
|
47
|
+
def test_without_time(self):
|
|
48
|
+
"""Test querying the API endpoint."""
|
|
49
|
+
|
|
50
|
+
forecast = pirateweather.load_forecast(self.api_key, self.lat, self.lng)
|
|
51
|
+
assert forecast.response.status_code == 200
|
|
52
|
+
|
|
53
|
+
def test_invalid_key(self):
|
|
54
|
+
"""Test querying the API endpoint with a invalid API key."""
|
|
55
|
+
|
|
56
|
+
self.api_key = "foo"
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
pirateweather.load_forecast(self.api_key, self.lat, self.lng)
|
|
60
|
+
|
|
61
|
+
pytest.fail(
|
|
62
|
+
"The previous line did not throw an exception"
|
|
63
|
+
) # the previous line should throw an exception
|
|
64
|
+
except requests.exceptions.HTTPError:
|
|
65
|
+
assert pytest.raises(requests.exceptions.HTTPError)
|
|
66
|
+
|
|
67
|
+
def test_invalid_param(self):
|
|
68
|
+
"""Test querying the API endpoint with an invalid parameter."""
|
|
69
|
+
|
|
70
|
+
self.lat = ""
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
pirateweather.load_forecast(self.api_key, self.lat, self.lng)
|
|
74
|
+
|
|
75
|
+
pytest.fail(
|
|
76
|
+
"The previous line did not throw an exception"
|
|
77
|
+
) # the previous line should throw an exception
|
|
78
|
+
except requests.exceptions.HTTPError:
|
|
79
|
+
assert pytest.raises(requests.exceptions.HTTPError)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BasicFunctionality(unittest.TestCase):
|
|
83
|
+
"""Test basic functionality of the library."""
|
|
84
|
+
|
|
85
|
+
@responses.activate
|
|
86
|
+
def setUp(self):
|
|
87
|
+
"""Set up the data to use in the next tests."""
|
|
88
|
+
|
|
89
|
+
URL = "https://api.pirateweather.net/forecast/foo/50.0,10.0?units=us&lang=en"
|
|
90
|
+
responses.add(
|
|
91
|
+
responses.GET,
|
|
92
|
+
URL,
|
|
93
|
+
body=open(os.path.join("./fixtures/test.json")).read(),
|
|
94
|
+
status=200,
|
|
95
|
+
content_type="application/json",
|
|
96
|
+
match_querystring=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
api_key = "foo"
|
|
100
|
+
lat = 50.0
|
|
101
|
+
lng = 10.0
|
|
102
|
+
self.fc = pirateweather.load_forecast(api_key, lat, lng)
|
|
103
|
+
|
|
104
|
+
assert responses.calls[0].request.url == URL
|
|
105
|
+
|
|
106
|
+
def test_current_temp(self):
|
|
107
|
+
"""Test the current temperature."""
|
|
108
|
+
|
|
109
|
+
fc_cur = self.fc.currently()
|
|
110
|
+
assert fc_cur.temperature == 55.81
|
|
111
|
+
|
|
112
|
+
def test_datablock_summary(self):
|
|
113
|
+
"""Test the hourly summary."""
|
|
114
|
+
|
|
115
|
+
hourl_data = self.fc.hourly()
|
|
116
|
+
assert hourl_data.summary == "Drizzle until this evening."
|
|
117
|
+
|
|
118
|
+
def test_datablock_icon(self):
|
|
119
|
+
"""Test the hourly data point icon."""
|
|
120
|
+
|
|
121
|
+
hourl_data = self.fc.hourly()
|
|
122
|
+
assert hourl_data.icon == "rain"
|
|
123
|
+
|
|
124
|
+
def test_datablock_not_available(self):
|
|
125
|
+
"""Test the minutely data block."""
|
|
126
|
+
|
|
127
|
+
minutely = self.fc.minutely()
|
|
128
|
+
assert minutely.data == []
|
|
129
|
+
|
|
130
|
+
def test_datapoint_number(self):
|
|
131
|
+
"""Test the number of data points returned by the data point."""
|
|
132
|
+
|
|
133
|
+
hourl_data = self.fc.hourly()
|
|
134
|
+
assert len(hourl_data.data) == 49
|
|
135
|
+
|
|
136
|
+
def test_datapoint_temp(self):
|
|
137
|
+
"""Test the first day minumum temperature."""
|
|
138
|
+
|
|
139
|
+
daily = self.fc.daily()
|
|
140
|
+
assert daily.data[0].temperatureMin == 50.73
|
|
141
|
+
|
|
142
|
+
def test_datapoint_string_repr(self):
|
|
143
|
+
"""Test the string representation of the currently data."""
|
|
144
|
+
|
|
145
|
+
currently = self.fc.currently()
|
|
146
|
+
|
|
147
|
+
assert (
|
|
148
|
+
f"{currently}"
|
|
149
|
+
== "<PirateWeatherDataPoint instance: Overcast at 2014-05-28 04:27:39>"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def test_datablock_string_repr(self):
|
|
153
|
+
"""Test the string representation of the hourly data."""
|
|
154
|
+
|
|
155
|
+
hourly = self.fc.hourly()
|
|
156
|
+
|
|
157
|
+
assert (
|
|
158
|
+
f"{hourly}"
|
|
159
|
+
== "<PirateWeatherDataBlock instance: Drizzle until this evening. with 49 PirateWeatherDataPoints>"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@raises(pirateweather.utils.PropertyUnavailable)
|
|
163
|
+
def test_datapoint_attribute_not_available(self):
|
|
164
|
+
"""Test fetching an invalid property on the daily block."""
|
|
165
|
+
|
|
166
|
+
daily = self.fc.daily()
|
|
167
|
+
daily.data[0].notavailable # noqa: B018
|
|
168
|
+
|
|
169
|
+
def test_apparentTemperature(self):
|
|
170
|
+
"""Test the first hour data block apparent temperature."""
|
|
171
|
+
|
|
172
|
+
hourly = self.fc.hourly()
|
|
173
|
+
apprentTemp = hourly.data[0].apparentTemperature
|
|
174
|
+
|
|
175
|
+
assert apprentTemp == 55.06
|
|
176
|
+
|
|
177
|
+
def test_alerts_length(self):
|
|
178
|
+
"""Test the length of the alerts block."""
|
|
179
|
+
|
|
180
|
+
alerts = self.fc.alerts()
|
|
181
|
+
assert len(alerts) == 0
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ForecastsWithAlerts(unittest.TestCase):
|
|
185
|
+
"""Test basic functionality of the library with alerts."""
|
|
186
|
+
|
|
187
|
+
@responses.activate
|
|
188
|
+
def setUp(self):
|
|
189
|
+
"""Set up the test data with alerts to use in the next tests."""
|
|
190
|
+
|
|
191
|
+
URL = "https://api.pirateweather.net/forecast/foo/50.0,10.0?units=us&lang=en"
|
|
192
|
+
responses.add(
|
|
193
|
+
responses.GET,
|
|
194
|
+
URL,
|
|
195
|
+
body=open(os.path.join("./fixtures/test_with_alerts.json")).read(),
|
|
196
|
+
status=200,
|
|
197
|
+
content_type="application/json",
|
|
198
|
+
match_querystring=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
api_key = "foo"
|
|
202
|
+
lat = 50.0
|
|
203
|
+
lng = 10.0
|
|
204
|
+
self.fc = pirateweather.load_forecast(api_key, lat, lng)
|
|
205
|
+
|
|
206
|
+
def test_alerts_length(self):
|
|
207
|
+
"""Test the length of the alerts block."""
|
|
208
|
+
|
|
209
|
+
alerts = self.fc.alerts()
|
|
210
|
+
assert len(alerts) == 2
|
|
211
|
+
|
|
212
|
+
def test_alert_title(self):
|
|
213
|
+
"""Test the title of the first alert."""
|
|
214
|
+
|
|
215
|
+
alerts = self.fc.alerts()
|
|
216
|
+
first_alert = alerts[0]
|
|
217
|
+
|
|
218
|
+
assert first_alert.title == "Excessive Heat Warning for Inyo, CA"
|
|
219
|
+
|
|
220
|
+
def test_alert_uri(self):
|
|
221
|
+
"""Test the first alert URI."""
|
|
222
|
+
|
|
223
|
+
alerts = self.fc.alerts()
|
|
224
|
+
first_alert = alerts[0]
|
|
225
|
+
|
|
226
|
+
assert (
|
|
227
|
+
first_alert.uri
|
|
228
|
+
== "http://alerts.weather.gov/cap/wwacapget.php?x=CA125159BB3908.ExcessiveHeatWarning.125159E830C0CA.VEFNPWVEF.8faae06d42ba631813492a6a6eae41bc"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def test_alert_time(self):
|
|
232
|
+
"""Test the first alert time."""
|
|
233
|
+
|
|
234
|
+
alerts = self.fc.alerts()
|
|
235
|
+
first_alert = alerts[0]
|
|
236
|
+
|
|
237
|
+
assert first_alert.time == 1402133400
|
|
238
|
+
|
|
239
|
+
@raises(pirateweather.utils.PropertyUnavailable)
|
|
240
|
+
def test_alert_property_does_not_exist(self):
|
|
241
|
+
"""Test fetching an invalid property on the alerts."""
|
|
242
|
+
|
|
243
|
+
alerts = self.fc.alerts()
|
|
244
|
+
first_alert = alerts[0]
|
|
245
|
+
|
|
246
|
+
first_alert.notarealproperty # noqa: B018
|
|
247
|
+
|
|
248
|
+
def test_alert_string_repr(self):
|
|
249
|
+
"""Test the string representation of the currently data."""
|
|
250
|
+
|
|
251
|
+
alerts = self.fc.alerts()
|
|
252
|
+
first_alert = alerts[0]
|
|
253
|
+
|
|
254
|
+
assert first_alert.time == 1402133400
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class BasicManualURL(unittest.TestCase):
|
|
258
|
+
"""Test basic URL functionality."""
|
|
259
|
+
|
|
260
|
+
@responses.activate
|
|
261
|
+
def setUp(self):
|
|
262
|
+
"""Set up the data to use in the next tests."""
|
|
263
|
+
|
|
264
|
+
URL = "http://test_url.com/"
|
|
265
|
+
responses.add(
|
|
266
|
+
responses.GET,
|
|
267
|
+
URL,
|
|
268
|
+
body=open(os.path.join("./fixtures/test.json")).read(),
|
|
269
|
+
status=200,
|
|
270
|
+
content_type="application/json",
|
|
271
|
+
match_querystring=True,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
self.forecast = pirateweather.manual("http://test_url.com/")
|
|
275
|
+
|
|
276
|
+
def test_current_temp(self):
|
|
277
|
+
"""Test the current temperature."""
|
|
278
|
+
|
|
279
|
+
fc_cur = self.forecast.currently()
|
|
280
|
+
assert fc_cur.temperature == 55.81
|
|
281
|
+
|
|
282
|
+
def test_datablock_summary(self):
|
|
283
|
+
"""Test the hourly data block summary."""
|
|
284
|
+
|
|
285
|
+
hourl_data = self.forecast.hourly()
|
|
286
|
+
assert hourl_data.summary == "Drizzle until this evening."
|