├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── apixu ├── __init__.py ├── client.py ├── errors.py └── tests │ ├── conditions_test.py │ ├── current_test.py │ ├── forecast_test.py │ ├── history_test.py │ ├── schema │ ├── __init__.py │ ├── conditions.json │ ├── current.json │ ├── forecast.json │ ├── history.json │ ├── schema.py │ └── search.json │ └── search_test.py ├── examples ├── app │ ├── .env.dist │ ├── .gitignore │ ├── README.md │ ├── app.py │ └── requirements.txt ├── conditions.py ├── current.py ├── django │ ├── .env.dist │ ├── .gitignore │ ├── README.md │ ├── api │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── manage.py │ ├── requirements.txt │ └── weather │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── exception.py │ │ ├── migrations │ │ └── __init__.py │ │ ├── models.py │ │ ├── urls.py │ │ └── views.py ├── flask │ ├── .env.dist │ ├── .gitignore │ ├── README.md │ ├── app.py │ └── requirements.txt ├── forecast.py ├── history.py └── search.py ├── requirements-dev.txt ├── requirements.txt └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.egg-info/ 4 | build/ 5 | dist/ 6 | venv/ 7 | *.egg 8 | .cache 9 | .pytest_cache/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Mzemo DWC LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: env run test 2 | 3 | ifndef PYVERSION 4 | PYVERSION=3 5 | endif 6 | 7 | ifndef APIXUKEY 8 | $(error APIXUKEY is not set) 9 | endif 10 | 11 | IMAGE=python:$(PYVERSION)-alpine 12 | TEST_CMD := 'python setup.py install && pip install -r requirements-dev.txt && pytest' 13 | 14 | env: 15 | docker run --rm -ti -v $(CURDIR):/src -w /src -e APIXUKEY=$(APIXUKEY) $(IMAGE) sh 16 | 17 | run: 18 | docker run --rm -ti -v $(CURDIR):/src -w /src -e APIXUKEY=$(APIXUKEY) $(IMAGE) sh -c 'python setup.py develop && sh -c "python $(FILE)"' 19 | 20 | test: 21 | docker run --rm -ti -v $(CURDIR):/src -w /src -e APIXUKEY=$(APIXUKEY) $(IMAGE) sh -c $(TEST_CMD) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apixu Python 2 | 3 | Python library for [Apixu Weather API](https://www.apixu.com/api.aspx) 4 | 5 | ## Requirements 6 | * [Python](https://www.python.org/downloads/) (version 2 or 3) 7 | * [pip](https://pip.pypa.io/en/stable/installing/) 8 | * [Git](https://git-scm.com/downloads) (optional) 9 | 10 | #### Windows 11 | * Download [Python](https://www.python.org/downloads/windows/) 12 | * Install [pip](https://pip.pypa.io/en/stable/installing/#do-i-need-to-install-pip) if necessary 13 | * Download [Git](https://git-scm.com/download/win) 14 | 15 | #### Ubuntu & Debian 16 | ``` 17 | apt update 18 | apt install -y python python-pip git 19 | ``` 20 | 21 | #### Alpine 22 | ``` 23 | apk update --no-cache 24 | apk add python py-pip git 25 | ``` 26 | 27 | #### CentOS 28 | ``` 29 | yum install -y epel-release 30 | yum install -y python python-pip git 31 | ``` 32 | 33 | ## Install Apixu client 34 | 35 | Choose the version you want to install from the [releases page](https://github.com/apixu/apixu-python/releases) 36 | or choose `master` to install the latest updates. 37 | 38 | #### pip 39 | ``` 40 | pip install git+https://github.com/apixu/apixu-python.git@vX.X.X 41 | ``` 42 | or 43 | ``` 44 | pip install git+https://github.com/apixu/apixu-python.git@master 45 | ``` 46 | or without Git 47 | ``` 48 | pip install https://github.com/apixu/apixu-python/archive/vX.X.X.zip 49 | ``` 50 | 51 | #### Requirements file 52 | Add to `requirements.txt` 53 | ``` 54 | git+https://github.com/apixu/apixu-python.git@vX.X.X 55 | ``` 56 | then 57 | ``` 58 | pip install -r requirements.txt 59 | ``` 60 | 61 | #### Manually 62 | ``` 63 | pip install requests setuptools 64 | git clone https://github.com/apixu/apixu-python --branch vX.X.X --single-branch # or download repository 65 | cd apixu-python 66 | git checkout vX.X.X 67 | python setup.py install 68 | ``` 69 | 70 | ## Usage and integration with frameworks 71 | 72 | See the [examples](./examples). 73 | 74 | ``` 75 | APIXUKEY=yourapikey python examples/.py 76 | ``` 77 | 78 | ## Documentation 79 | 80 | https://www.apixu.com/doc/ 81 | 82 | ## Development 83 | 84 | You can use with Docker. See [Makefile](Makefile). 85 | 86 | Run tests: 87 | ``` 88 | make test PYVERSION=3 APIXUKEY=yourapikey 89 | ``` 90 | 91 | Enter environment: 92 | ``` 93 | make env PYVERSION=3 APIXUKEY=yourapikey 94 | python setup.py develop 95 | pip install -r requirements-dev.txt 96 | pytest apixu/tests/*.py 97 | pylint apixu 98 | ``` 99 | 100 | Run example file: 101 | ``` 102 | make run PYVERSION=3 APIXUKEY=yourapikey FILE=examples/search.py 103 | ``` 104 | -------------------------------------------------------------------------------- /apixu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apixu/apixu-python/370216999346d5caf7f8dc6724b5766dcc6da25d/apixu/__init__.py -------------------------------------------------------------------------------- /apixu/client.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import requests 3 | 4 | API_URL = 'http://api.apixu.com' 5 | API_VERSION = '1' 6 | FORMAT = 'json' 7 | HTTP_TIMEOUT = 20 8 | DOC_WEATHER_CONDITIONS_URL = 'https://www.apixu.com/doc/Apixu_weather_conditions.%s' 9 | 10 | 11 | class ApixuException(Exception): 12 | def __init__(self, message, code): 13 | self.message = message 14 | self.code = code 15 | message = 'Error code %s: "%s"' % (code, message) 16 | super(ApixuException, self).__init__(message) 17 | 18 | 19 | class ApixuClient: 20 | def __init__(self, api_key=None, api_url=API_URL, lang=None): 21 | self.api_key = api_key 22 | self.api_url = api_url.rstrip('/') 23 | self.lang = lang 24 | 25 | def _get(self, url, args=None): 26 | new_args = {} 27 | if self.api_key: 28 | new_args['key'] = self.api_key 29 | new_args.update(args or {}) 30 | response = requests.get(url, params=new_args, timeout=HTTP_TIMEOUT) 31 | res = response.json() 32 | if 'error' in res: 33 | err_msg = res['error'].get('message') 34 | err_code = res['error'].get('code') 35 | raise ApixuException(message=err_msg, code=err_code) 36 | 37 | return res 38 | 39 | def _url(self, method): 40 | return '%s/v%s/%s.%s' % (self.api_url, API_VERSION, method, FORMAT) 41 | 42 | def conditions(self): 43 | url = DOC_WEATHER_CONDITIONS_URL % FORMAT 44 | 45 | return self._get(url) 46 | 47 | def current(self, q=None): 48 | url = self._url('current') 49 | args = {} 50 | if q: 51 | args['q'] = q 52 | if self.lang: 53 | args['lang'] = self.lang 54 | 55 | return self._get(url, args) 56 | 57 | def search(self, q=None): 58 | url = self._url('search') 59 | args = {} 60 | if q: 61 | args['q'] = q 62 | 63 | return self._get(url, args) 64 | 65 | def forecast(self, q=None, days=None, hour=None): 66 | url = self._url('forecast') 67 | args = {} 68 | if q: 69 | args['q'] = q 70 | if days: 71 | args['days'] = days 72 | if hour: 73 | args['hour'] = hour 74 | if self.lang: 75 | args['lang'] = self.lang 76 | 77 | return self._get(url, args) 78 | 79 | def history(self, q=None, since=None, until=None): 80 | url = self._url('history') 81 | args = {} 82 | if q: 83 | args['q'] = q 84 | if since: 85 | if not isinstance(since, datetime.date): 86 | raise ApixuException(message='"since" must be a date', code=0) 87 | args['dt'] = since.strftime('%Y-%m-%d') 88 | if until: 89 | if not isinstance(until, datetime.date): 90 | raise ApixuException(message='"until" must be a date', code=0) 91 | args['end_dt'] = until.strftime('%Y-%m-%d') 92 | if self.lang: 93 | args['lang'] = self.lang 94 | 95 | return self._get(url, args) 96 | -------------------------------------------------------------------------------- /apixu/errors.py: -------------------------------------------------------------------------------- 1 | API_KEY_NOT_PROVIDED = 1002 2 | QUERY_PARAMETER_NOT_PROVIDED = 1003 3 | API_REQUEST_URL_INVALID = 1005 4 | NO_LOCATION_FOUND_FOR_QUERY = 1006 5 | API_KEY_INVALID = 2006 6 | API_KEY_MONTHLY_CALLS_QUOTA_EXCEEDED = 2007 7 | API_KEY_DISABLED = 2008 8 | INTERNAL_APPLICATION_ERROR = 9999 9 | -------------------------------------------------------------------------------- /apixu/tests/conditions_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from schema import schema 3 | from apixu.client import ApixuClient 4 | from jsonschema import validate 5 | 6 | 7 | class CurrentTestCase(unittest.TestCase): 8 | 9 | @staticmethod 10 | def test_conditions(): 11 | client = ApixuClient() 12 | 13 | conditions = client.conditions() 14 | validate(conditions, schema.read("conditions.json")) 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /apixu/tests/current_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from schema import schema 3 | import os 4 | from apixu.client import ApixuClient, ApixuException 5 | from apixu import errors 6 | from jsonschema import validate 7 | 8 | 9 | class CurrentTestCase(unittest.TestCase): 10 | 11 | @staticmethod 12 | def test_current(): 13 | api_key = os.environ['APIXUKEY'] 14 | client = ApixuClient(api_key) 15 | 16 | current = client.current('London') 17 | validate(current, schema.read('current.json')) 18 | 19 | def test_current_invalid_api_key(self): 20 | client = ApixuClient('INVALID_KEY') 21 | with self.assertRaises(ApixuException) as cm: 22 | client.current() 23 | 24 | self.assertEqual(cm.exception.code, errors.API_KEY_INVALID) 25 | 26 | def test_current_no_api_key(self): 27 | client = ApixuClient() 28 | with self.assertRaises(ApixuException) as cm: 29 | client.current() 30 | 31 | self.assertEqual(cm.exception.code, errors.API_KEY_NOT_PROVIDED) 32 | 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /apixu/tests/forecast_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from schema import schema 3 | import os 4 | from apixu.client import ApixuClient, ApixuException 5 | from apixu import errors 6 | from jsonschema import validate 7 | 8 | 9 | class ForecastTestCase(unittest.TestCase): 10 | 11 | @staticmethod 12 | def test_forecast(): 13 | api_key = os.environ['APIXUKEY'] 14 | client = ApixuClient(api_key) 15 | 16 | forecast = client.forecast('London', 1, 12) 17 | validate(forecast, schema.read("forecast.json")) 18 | 19 | def test_forecast_invalid_api_key(self): 20 | client = ApixuClient('INVALID_KEY') 21 | with self.assertRaises(ApixuException) as cm: 22 | client.forecast() 23 | 24 | self.assertEqual(cm.exception.code, errors.API_KEY_INVALID) 25 | 26 | def test_forecast_no_api_key(self): 27 | client = ApixuClient() 28 | with self.assertRaises(ApixuException) as cm: 29 | client.forecast() 30 | 31 | self.assertEqual(cm.exception.code, errors.API_KEY_NOT_PROVIDED) 32 | 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /apixu/tests/history_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from schema import schema 3 | import os 4 | from apixu.client import ApixuClient, ApixuException 5 | from apixu import errors 6 | from jsonschema import validate 7 | import datetime 8 | 9 | 10 | class HistoryTestCase(unittest.TestCase): 11 | 12 | @staticmethod 13 | def test_history(): 14 | api_key = os.environ['APIXUKEY'] 15 | client = ApixuClient(api_key) 16 | 17 | now = datetime.datetime.now() 18 | history = client.history( 19 | q='London', 20 | since=datetime.date(now.year, now.month, now.day), 21 | until=datetime.date(now.year, now.month, now.day), 22 | ) 23 | validate(history, schema.read("history.json")) 24 | 25 | def test_history_invalid_api_key(self): 26 | client = ApixuClient('INVALID_KEY') 27 | with self.assertRaises(ApixuException) as cm: 28 | client.history() 29 | 30 | self.assertEqual(cm.exception.code, errors.API_KEY_INVALID) 31 | 32 | def test_history_no_api_key(self): 33 | client = ApixuClient() 34 | with self.assertRaises(ApixuException) as cm: 35 | client.history() 36 | 37 | self.assertEqual(cm.exception.code, errors.API_KEY_NOT_PROVIDED) 38 | 39 | def test_history_invalid_since(self): 40 | client = ApixuClient() 41 | with self.assertRaises(ApixuException) as cm: 42 | client.history(since='notdate') 43 | 44 | self.assertEqual(cm.exception.code, 0) 45 | 46 | def test_history_invalid_until(self): 47 | client = ApixuClient() 48 | with self.assertRaises(ApixuException) as cm: 49 | client.history(until='notdate') 50 | 51 | self.assertEqual(cm.exception.code, 0) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /apixu/tests/schema/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apixu/apixu-python/370216999346d5caf7f8dc6724b5766dcc6da25d/apixu/tests/schema/__init__.py -------------------------------------------------------------------------------- /apixu/tests/schema/conditions.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "minItems": 1, 5 | "items": [ 6 | { 7 | "type": "object", 8 | "properties": { 9 | "code": { 10 | "type": "integer" 11 | }, 12 | "day": { 13 | "type": "string" 14 | }, 15 | "night": { 16 | "type": "string" 17 | }, 18 | "icon": { 19 | "type": "integer" 20 | } 21 | }, 22 | "required": [ 23 | "code", 24 | "day", 25 | "night", 26 | "icon" 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /apixu/tests/schema/current.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "location": { 6 | "type": "object", 7 | "properties": { 8 | "name": { 9 | "type": "string" 10 | }, 11 | "region": { 12 | "type": "string" 13 | }, 14 | "country": { 15 | "type": "string" 16 | }, 17 | "lat": { 18 | "type": "number" 19 | }, 20 | "lon": { 21 | "type": "number" 22 | }, 23 | "tz_id": { 24 | "type": "string" 25 | }, 26 | "localtime_epoch": { 27 | "type": "integer" 28 | }, 29 | "localtime": { 30 | "type": "string" 31 | } 32 | }, 33 | "required": [ 34 | "name", 35 | "region", 36 | "country", 37 | "lat", 38 | "lon", 39 | "tz_id", 40 | "localtime_epoch", 41 | "localtime" 42 | ] 43 | }, 44 | "current": { 45 | "type": "object", 46 | "properties": { 47 | "last_updated_epoch": { 48 | "type": "integer" 49 | }, 50 | "last_updated": { 51 | "type": "string" 52 | }, 53 | "temp_c": { 54 | "type": "number" 55 | }, 56 | "temp_f": { 57 | "type": "number" 58 | }, 59 | "is_day": { 60 | "type": "integer" 61 | }, 62 | "condition": { 63 | "type": "object", 64 | "properties": { 65 | "text": { 66 | "type": "string" 67 | }, 68 | "icon": { 69 | "type": "string" 70 | }, 71 | "code": { 72 | "type": "integer" 73 | } 74 | }, 75 | "required": [ 76 | "text", 77 | "icon", 78 | "code" 79 | ] 80 | }, 81 | "wind_mph": { 82 | "type": "number" 83 | }, 84 | "wind_kph": { 85 | "type": "number" 86 | }, 87 | "wind_degree": { 88 | "type": "integer" 89 | }, 90 | "wind_dir": { 91 | "type": "string" 92 | }, 93 | "pressure_mb": { 94 | "type": "number" 95 | }, 96 | "pressure_in": { 97 | "type": "number" 98 | }, 99 | "precip_mm": { 100 | "type": "number" 101 | }, 102 | "precip_in": { 103 | "type": "number" 104 | }, 105 | "humidity": { 106 | "type": "integer" 107 | }, 108 | "cloud": { 109 | "type": "integer" 110 | }, 111 | "feelslike_c": { 112 | "type": "number" 113 | }, 114 | "feelslike_f": { 115 | "type": "number" 116 | }, 117 | "vis_km": { 118 | "type": "number" 119 | }, 120 | "vis_miles": { 121 | "type": "number" 122 | }, 123 | "uv": { 124 | "type": "number" 125 | } 126 | }, 127 | "required": [ 128 | "last_updated_epoch", 129 | "last_updated", 130 | "temp_c", 131 | "temp_f", 132 | "is_day", 133 | "condition", 134 | "wind_mph", 135 | "wind_kph", 136 | "wind_degree", 137 | "wind_dir", 138 | "pressure_mb", 139 | "pressure_in", 140 | "precip_mm", 141 | "precip_in", 142 | "humidity", 143 | "cloud", 144 | "feelslike_c", 145 | "feelslike_f", 146 | "vis_km", 147 | "vis_miles", 148 | "uv" 149 | ] 150 | } 151 | }, 152 | "required": [ 153 | "location", 154 | "current" 155 | ] 156 | } -------------------------------------------------------------------------------- /apixu/tests/schema/forecast.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "location": { 6 | "type": "object", 7 | "properties": { 8 | "name": { 9 | "type": "string" 10 | }, 11 | "region": { 12 | "type": "string" 13 | }, 14 | "country": { 15 | "type": "string" 16 | }, 17 | "lat": { 18 | "type": "number" 19 | }, 20 | "lon": { 21 | "type": "number" 22 | }, 23 | "tz_id": { 24 | "type": "string" 25 | }, 26 | "localtime_epoch": { 27 | "type": "integer" 28 | }, 29 | "localtime": { 30 | "type": "string" 31 | } 32 | }, 33 | "required": [ 34 | "name", 35 | "region", 36 | "country", 37 | "lat", 38 | "lon", 39 | "tz_id", 40 | "localtime_epoch", 41 | "localtime" 42 | ] 43 | }, 44 | "current": { 45 | "type": "object", 46 | "properties": { 47 | "last_updated_epoch": { 48 | "type": "integer" 49 | }, 50 | "last_updated": { 51 | "type": "string" 52 | }, 53 | "temp_c": { 54 | "type": "number" 55 | }, 56 | "temp_f": { 57 | "type": "number" 58 | }, 59 | "is_day": { 60 | "type": "integer" 61 | }, 62 | "condition": { 63 | "type": "object", 64 | "properties": { 65 | "text": { 66 | "type": "string" 67 | }, 68 | "icon": { 69 | "type": "string" 70 | }, 71 | "code": { 72 | "type": "integer" 73 | } 74 | }, 75 | "required": [ 76 | "text", 77 | "icon", 78 | "code" 79 | ] 80 | }, 81 | "wind_mph": { 82 | "type": "number" 83 | }, 84 | "wind_kph": { 85 | "type": "number" 86 | }, 87 | "wind_degree": { 88 | "type": "integer" 89 | }, 90 | "wind_dir": { 91 | "type": "string" 92 | }, 93 | "pressure_mb": { 94 | "type": "number" 95 | }, 96 | "pressure_in": { 97 | "type": "number" 98 | }, 99 | "precip_mm": { 100 | "type": "number" 101 | }, 102 | "precip_in": { 103 | "type": "number" 104 | }, 105 | "humidity": { 106 | "type": "integer" 107 | }, 108 | "cloud": { 109 | "type": "integer" 110 | }, 111 | "feelslike_c": { 112 | "type": "number" 113 | }, 114 | "feelslike_f": { 115 | "type": "number" 116 | }, 117 | "vis_km": { 118 | "type": "number" 119 | }, 120 | "vis_miles": { 121 | "type": "number" 122 | }, 123 | "uv": { 124 | "type": "number" 125 | } 126 | }, 127 | "required": [ 128 | "last_updated_epoch", 129 | "last_updated", 130 | "temp_c", 131 | "temp_f", 132 | "is_day", 133 | "condition", 134 | "wind_mph", 135 | "wind_kph", 136 | "wind_degree", 137 | "wind_dir", 138 | "pressure_mb", 139 | "pressure_in", 140 | "precip_mm", 141 | "precip_in", 142 | "humidity", 143 | "cloud", 144 | "feelslike_c", 145 | "feelslike_f", 146 | "vis_km", 147 | "vis_miles", 148 | "uv" 149 | ] 150 | }, 151 | "forecast": { 152 | "type": "object", 153 | "properties": { 154 | "forecastday": { 155 | "type": "array", 156 | "items": [ 157 | { 158 | "type": "object", 159 | "properties": { 160 | "date": { 161 | "type": "string" 162 | }, 163 | "date_epoch": { 164 | "type": "integer" 165 | }, 166 | "day": { 167 | "type": "object", 168 | "properties": { 169 | "maxtemp_c": { 170 | "type": "number" 171 | }, 172 | "maxtemp_f": { 173 | "type": "number" 174 | }, 175 | "mintemp_c": { 176 | "type": "number" 177 | }, 178 | "mintemp_f": { 179 | "type": "number" 180 | }, 181 | "avgtemp_c": { 182 | "type": "number" 183 | }, 184 | "avgtemp_f": { 185 | "type": "number" 186 | }, 187 | "maxwind_mph": { 188 | "type": "number" 189 | }, 190 | "maxwind_kph": { 191 | "type": "number" 192 | }, 193 | "totalprecip_mm": { 194 | "type": "number" 195 | }, 196 | "totalprecip_in": { 197 | "type": "number" 198 | }, 199 | "avgvis_km": { 200 | "type": "number" 201 | }, 202 | "avgvis_miles": { 203 | "type": "number" 204 | }, 205 | "avghumidity": { 206 | "type": "number" 207 | }, 208 | "condition": { 209 | "type": "object", 210 | "properties": { 211 | "text": { 212 | "type": "string" 213 | }, 214 | "icon": { 215 | "type": "string" 216 | }, 217 | "code": { 218 | "type": "integer" 219 | } 220 | }, 221 | "required": [ 222 | "text", 223 | "icon", 224 | "code" 225 | ] 226 | }, 227 | "uv": { 228 | "type": "number" 229 | } 230 | }, 231 | "required": [ 232 | "maxtemp_c", 233 | "maxtemp_f", 234 | "mintemp_c", 235 | "mintemp_f", 236 | "avgtemp_c", 237 | "avgtemp_f", 238 | "maxwind_mph", 239 | "maxwind_kph", 240 | "totalprecip_mm", 241 | "totalprecip_in", 242 | "avgvis_km", 243 | "avgvis_miles", 244 | "avghumidity", 245 | "condition", 246 | "uv" 247 | ] 248 | }, 249 | "astro": { 250 | "type": "object", 251 | "properties": { 252 | "sunrise": { 253 | "type": "string" 254 | }, 255 | "sunset": { 256 | "type": "string" 257 | }, 258 | "moonrise": { 259 | "type": "string" 260 | }, 261 | "moonset": { 262 | "type": "string" 263 | } 264 | }, 265 | "required": [ 266 | "sunrise", 267 | "sunset", 268 | "moonrise", 269 | "moonset" 270 | ] 271 | }, 272 | "hour": { 273 | "type": "array", 274 | "items" : [ 275 | { 276 | "type": "object", 277 | "properties": { 278 | "time_epoch": { 279 | "type": "integer" 280 | }, 281 | "time": { 282 | "type": "string" 283 | }, 284 | "temp_c": { 285 | "type": "number" 286 | }, 287 | "temp_f": { 288 | "type": "number" 289 | }, 290 | "is_day": { 291 | "type": "integer" 292 | }, 293 | "condition": { 294 | "type": "object", 295 | "properties": { 296 | "text": { 297 | "type": "string" 298 | }, 299 | "icon": { 300 | "type": "string" 301 | }, 302 | "code": { 303 | "type": "integer" 304 | } 305 | }, 306 | "required": [ 307 | "text", 308 | "icon", 309 | "code" 310 | ] 311 | }, 312 | "wind_mph": { 313 | "type": "number" 314 | }, 315 | "wind_kph": { 316 | "type": "number" 317 | }, 318 | "wind_degree": { 319 | "type": "integer" 320 | }, 321 | "wind_dir": { 322 | "type": "string" 323 | }, 324 | "pressure_mb": { 325 | "type": "number" 326 | }, 327 | "pressure_in": { 328 | "type": "number" 329 | }, 330 | "precip_mm": { 331 | "type": "number" 332 | }, 333 | "precip_in": { 334 | "type": "number" 335 | }, 336 | "humidity": { 337 | "type": "integer" 338 | }, 339 | "cloud": { 340 | "type": "integer" 341 | }, 342 | "feelslike_c": { 343 | "type": "number" 344 | }, 345 | "feelslike_f": { 346 | "type": "number" 347 | }, 348 | "vis_km": { 349 | "type": "number" 350 | }, 351 | "vis_miles": { 352 | "type": "number" 353 | }, 354 | "uv": { 355 | "type": "number" 356 | }, 357 | "gust_mph": { 358 | "type": "number" 359 | }, 360 | "gust_kph": { 361 | "type": "number" 362 | } 363 | }, 364 | "required": [ 365 | "time_epoch", 366 | "time", 367 | "temp_c", 368 | "temp_f", 369 | "is_day", 370 | "condition", 371 | "wind_mph", 372 | "wind_kph", 373 | "wind_degree", 374 | "wind_dir", 375 | "pressure_mb", 376 | "pressure_in", 377 | "precip_mm", 378 | "precip_in", 379 | "humidity", 380 | "cloud", 381 | "feelslike_c", 382 | "feelslike_f", 383 | "vis_km", 384 | "vis_miles", 385 | "gust_mph", 386 | "gust_kph" 387 | ] 388 | } 389 | ] 390 | } 391 | }, 392 | "required": [ 393 | "date", 394 | "date_epoch", 395 | "day", 396 | "astro" 397 | ] 398 | } 399 | ] 400 | } 401 | }, 402 | "required": [ 403 | "forecastday" 404 | ] 405 | } 406 | }, 407 | "required": [ 408 | "location", 409 | "current", 410 | "forecast" 411 | ] 412 | } -------------------------------------------------------------------------------- /apixu/tests/schema/history.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "location": { 6 | "type": "object", 7 | "properties": { 8 | "name": { 9 | "type": "string" 10 | }, 11 | "region": { 12 | "type": "string" 13 | }, 14 | "country": { 15 | "type": "string" 16 | }, 17 | "lat": { 18 | "type": "number" 19 | }, 20 | "lon": { 21 | "type": "number" 22 | }, 23 | "tz_id": { 24 | "type": "string" 25 | }, 26 | "localtime_epoch": { 27 | "type": "integer" 28 | }, 29 | "localtime": { 30 | "type": "string" 31 | } 32 | }, 33 | "required": [ 34 | "name", 35 | "region", 36 | "country", 37 | "lat", 38 | "lon", 39 | "tz_id", 40 | "localtime_epoch", 41 | "localtime" 42 | ] 43 | }, 44 | "forecast": { 45 | "type": "object", 46 | "properties": { 47 | "forecastday": { 48 | "type": "array", 49 | "items": [ 50 | { 51 | "type": "object", 52 | "properties": { 53 | "date": { 54 | "type": "string" 55 | }, 56 | "date_epoch": { 57 | "type": "integer" 58 | }, 59 | "day": { 60 | "type": "object", 61 | "properties": { 62 | "maxtemp_c": { 63 | "type": "number" 64 | }, 65 | "maxtemp_f": { 66 | "type": "number" 67 | }, 68 | "mintemp_c": { 69 | "type": "number" 70 | }, 71 | "mintemp_f": { 72 | "type": "number" 73 | }, 74 | "avgtemp_c": { 75 | "type": "number" 76 | }, 77 | "avgtemp_f": { 78 | "type": "number" 79 | }, 80 | "maxwind_mph": { 81 | "type": "number" 82 | }, 83 | "maxwind_kph": { 84 | "type": "number" 85 | }, 86 | "totalprecip_mm": { 87 | "type": "number" 88 | }, 89 | "totalprecip_in": { 90 | "type": "number" 91 | }, 92 | "avgvis_km": { 93 | "type": "number" 94 | }, 95 | "avgvis_miles": { 96 | "type": "number" 97 | }, 98 | "avghumidity": { 99 | "type": "number" 100 | }, 101 | "condition": { 102 | "type": "object", 103 | "properties": { 104 | "text": { 105 | "type": "string" 106 | }, 107 | "icon": { 108 | "type": "string" 109 | }, 110 | "code": { 111 | "type": "integer" 112 | } 113 | }, 114 | "required": [ 115 | "text", 116 | "icon", 117 | "code" 118 | ] 119 | }, 120 | "uv": { 121 | "type": "number" 122 | } 123 | }, 124 | "required": [ 125 | "maxtemp_c", 126 | "maxtemp_f", 127 | "mintemp_c", 128 | "mintemp_f", 129 | "avgtemp_c", 130 | "avgtemp_f", 131 | "maxwind_mph", 132 | "maxwind_kph", 133 | "totalprecip_mm", 134 | "totalprecip_in", 135 | "avgvis_km", 136 | "avgvis_miles", 137 | "avghumidity", 138 | "condition", 139 | "uv" 140 | ] 141 | }, 142 | "astro": { 143 | "type": "object", 144 | "properties": { 145 | "sunrise": { 146 | "type": "string" 147 | }, 148 | "sunset": { 149 | "type": "string" 150 | }, 151 | "moonrise": { 152 | "type": "string" 153 | }, 154 | "moonset": { 155 | "type": "string" 156 | }, 157 | "moon_phase": { 158 | "type": "string" 159 | }, 160 | "moon_illumination": { 161 | "type": "string" 162 | } 163 | }, 164 | "required": [ 165 | "sunrise", 166 | "sunset", 167 | "moonrise", 168 | "moonset", 169 | "moon_phase", 170 | "moon_illumination" 171 | ] 172 | }, 173 | "hour": { 174 | "type": "array", 175 | "items": [ 176 | { 177 | "type": "object", 178 | "properties": { 179 | "time_epoch": { 180 | "type": "integer" 181 | }, 182 | "time": { 183 | "type": "string" 184 | }, 185 | "temp_c": { 186 | "type": "number" 187 | }, 188 | "temp_f": { 189 | "type": "number" 190 | }, 191 | "is_day": { 192 | "type": "integer" 193 | }, 194 | "condition": { 195 | "type": "object", 196 | "properties": { 197 | "text": { 198 | "type": "string" 199 | }, 200 | "icon": { 201 | "type": "string" 202 | }, 203 | "code": { 204 | "type": "integer" 205 | } 206 | }, 207 | "required": [ 208 | "text", 209 | "icon", 210 | "code" 211 | ] 212 | }, 213 | "wind_mph": { 214 | "type": "number" 215 | }, 216 | "wind_kph": { 217 | "type": "number" 218 | }, 219 | "wind_degree": { 220 | "type": "integer" 221 | }, 222 | "wind_dir": { 223 | "type": "string" 224 | }, 225 | "pressure_mb": { 226 | "type": "number" 227 | }, 228 | "pressure_in": { 229 | "type": "number" 230 | }, 231 | "precip_mm": { 232 | "type": "number" 233 | }, 234 | "precip_in": { 235 | "type": "number" 236 | }, 237 | "humidity": { 238 | "type": "integer" 239 | }, 240 | "cloud": { 241 | "type": "integer" 242 | }, 243 | "feelslike_c": { 244 | "type": "number" 245 | }, 246 | "feelslike_f": { 247 | "type": "number" 248 | }, 249 | "windchill_c": { 250 | "type": "number" 251 | }, 252 | "windchill_f": { 253 | "type": "number" 254 | }, 255 | "heatindex_c": { 256 | "type": "number" 257 | }, 258 | "heatindex_f": { 259 | "type": "number" 260 | }, 261 | "dewpoint_c": { 262 | "type": "number" 263 | }, 264 | "dewpoint_f": { 265 | "type": "number" 266 | }, 267 | "will_it_rain": { 268 | "type": "integer" 269 | }, 270 | "chance_of_rain": { 271 | "type": "string" 272 | }, 273 | "will_it_snow": { 274 | "type": "integer" 275 | }, 276 | "chance_of_snow": { 277 | "type": "string" 278 | }, 279 | "vis_km": { 280 | "type": "number" 281 | }, 282 | "vis_miles": { 283 | "type": "number" 284 | }, 285 | "gust_mph": { 286 | "type": "number" 287 | }, 288 | "gust_kph": { 289 | "type": "number" 290 | } 291 | }, 292 | "required": [ 293 | "time_epoch", 294 | "time", 295 | "temp_c", 296 | "temp_f", 297 | "is_day", 298 | "condition", 299 | "wind_mph", 300 | "wind_kph", 301 | "wind_degree", 302 | "wind_dir", 303 | "pressure_mb", 304 | "pressure_in", 305 | "precip_mm", 306 | "precip_in", 307 | "humidity", 308 | "cloud", 309 | "feelslike_c", 310 | "feelslike_f", 311 | "windchill_c", 312 | "windchill_f", 313 | "heatindex_c", 314 | "heatindex_f", 315 | "dewpoint_c", 316 | "dewpoint_f", 317 | "will_it_rain", 318 | "chance_of_rain", 319 | "will_it_snow", 320 | "chance_of_snow", 321 | "vis_km", 322 | "vis_miles", 323 | "gust_mph", 324 | "gust_kph" 325 | ] 326 | } 327 | ] 328 | } 329 | }, 330 | "required": [ 331 | "date", 332 | "date_epoch", 333 | "day", 334 | "astro", 335 | "hour" 336 | ] 337 | } 338 | ] 339 | } 340 | }, 341 | "required": [ 342 | "forecastday" 343 | ] 344 | } 345 | }, 346 | "required": [ 347 | "location", 348 | "forecast" 349 | ] 350 | } -------------------------------------------------------------------------------- /apixu/tests/schema/schema.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | def read(filename): 6 | filename = os.path.dirname(os.path.realpath(__file__)) + "/" + filename 7 | with open(filename) as f: 8 | data = json.load(f) 9 | return data 10 | -------------------------------------------------------------------------------- /apixu/tests/schema/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": [ 5 | { 6 | "type": "object", 7 | "properties": { 8 | "id": { 9 | "type": "integer" 10 | }, 11 | "name": { 12 | "type": "string" 13 | }, 14 | "region": { 15 | "type": "string" 16 | }, 17 | "country": { 18 | "type": "string" 19 | }, 20 | "lat": { 21 | "type": "number" 22 | }, 23 | "lon": { 24 | "type": "number" 25 | }, 26 | "url": { 27 | "type": "string" 28 | } 29 | }, 30 | "required": [ 31 | "id", 32 | "name", 33 | "region", 34 | "country", 35 | "lat", 36 | "lon", 37 | "url" 38 | ] 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /apixu/tests/search_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from schema import schema 3 | import os 4 | from apixu.client import ApixuClient, ApixuException 5 | from apixu import errors 6 | from jsonschema import validate 7 | 8 | 9 | class SearchTestCase(unittest.TestCase): 10 | 11 | @staticmethod 12 | def test_search(): 13 | api_key = os.environ['APIXUKEY'] 14 | client = ApixuClient(api_key) 15 | 16 | history = client.search(q='London') 17 | validate(history, schema.read("search.json")) 18 | 19 | def test_search_invalid_api_key(self): 20 | client = ApixuClient('INVALID_KEY') 21 | with self.assertRaises(ApixuException) as cm: 22 | client.search() 23 | 24 | self.assertEqual(cm.exception.code, errors.API_KEY_INVALID) 25 | 26 | def test_search_no_api_key(self): 27 | client = ApixuClient() 28 | with self.assertRaises(ApixuException) as cm: 29 | client.search() 30 | 31 | self.assertEqual(cm.exception.code, errors.API_KEY_NOT_PROVIDED) 32 | 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /examples/app/.env.dist: -------------------------------------------------------------------------------- 1 | APIXUKEY= -------------------------------------------------------------------------------- /examples/app/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | *.egg-info/ 4 | build/ 5 | dist/ 6 | *.egg 7 | .cache 8 | .pytest_cache/ 9 | -------------------------------------------------------------------------------- /examples/app/README.md: -------------------------------------------------------------------------------- 1 | # APP 2 | 3 | ## Basic app using Apixu 4 | 5 | ### Requirements 6 | * Python 7 | * pip 8 | 9 | ### Setup app 10 | 11 | Set APIXUKEY in the .env file. 12 | ``` 13 | cp .env.dist .env 14 | ``` 15 | 16 | Set Apixu library dependency in [requirements.txt](./requirements.txt). 17 | 18 | ### Install dependencies 19 | ``` 20 | pip install -r requirements.txt 21 | ``` 22 | 23 | ### Run app 24 | ``` 25 | python app.py 26 | ``` 27 | -------------------------------------------------------------------------------- /examples/app/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import dotenv_values 3 | from apixu.client import ApixuClient 4 | 5 | os.environ.update(dotenv_values()) 6 | 7 | api_key = os.environ['APIXUKEY'] 8 | client = ApixuClient(api_key) 9 | 10 | query = 'London' 11 | current = client.current(q=query) 12 | 13 | print(current['location']) 14 | -------------------------------------------------------------------------------- /examples/app/requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv==0.10.1 2 | https://github.com/apixu/apixu-python/archive/vX.X.X.zip 3 | -------------------------------------------------------------------------------- /examples/conditions.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from apixu.client import ApixuClient 4 | 5 | api_key = os.environ['APIXUKEY'] 6 | client = ApixuClient(api_key) 7 | 8 | conditions = client.conditions() 9 | 10 | for condition in conditions: 11 | print(condition['code']) 12 | print(condition['day']) 13 | print(condition['icon']) 14 | print('\n') 15 | 16 | ''' 17 | [ 18 | { 19 | "code" : 1000, 20 | "day" : "Sunny", 21 | "night" : "Clear", 22 | "icon" : 113 23 | } 24 | ] 25 | ''' 26 | -------------------------------------------------------------------------------- /examples/current.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from apixu.client import ApixuClient 4 | 5 | api_key = os.environ['APIXUKEY'] 6 | client = ApixuClient(api_key=api_key, lang="fr") 7 | 8 | current = client.current(q='London') 9 | 10 | print(current['location']['name']) 11 | print(current['location']['region']) 12 | 13 | print(current['current']['last_updated_epoch']) 14 | print(current['current']['condition']['text']) 15 | 16 | ''' 17 | { 18 | "location":{ 19 | "name":"London", 20 | "region":"City of London, Greater London", 21 | "country":"United Kingdom", 22 | "lat":51.52, 23 | "lon":-0.11, 24 | "tz_id":"Europe/London", 25 | "localtime_epoch":1548103139, 26 | "localtime":"2019-01-21 20:38" 27 | }, 28 | "current":{ 29 | "last_updated_epoch":1548102624, 30 | "last_updated":"2019-01-21 20:30", 31 | "temp_c":4.0, 32 | "temp_f":39.2, 33 | "is_day":0, 34 | "condition":{ 35 | "text":"Clear", 36 | "icon":"//cdn.apixu.com/weather/64x64/night/113.png", 37 | "code":1000 38 | }, 39 | "wind_mph":6.9, 40 | "wind_kph":11.2, 41 | "wind_degree":210, 42 | "wind_dir":"SSW", 43 | "pressure_mb":1015.0, 44 | "pressure_in":30.4, 45 | "precip_mm":0.0, 46 | "precip_in":0.0, 47 | "humidity":81, 48 | "cloud":0, 49 | "feelslike_c":1.2, 50 | "feelslike_f":34.2, 51 | "vis_km":10.0, 52 | "vis_miles":6.0, 53 | "uv":0.0 54 | } 55 | } 56 | ''' 57 | -------------------------------------------------------------------------------- /examples/django/.env.dist: -------------------------------------------------------------------------------- 1 | SECRET_KEY= 2 | APIXUKEY= 3 | -------------------------------------------------------------------------------- /examples/django/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | *.egg-info/ 4 | build/ 5 | dist/ 6 | *.egg 7 | .cache 8 | .pytest_cache/ 9 | db.sqlite3 10 | -------------------------------------------------------------------------------- /examples/django/README.md: -------------------------------------------------------------------------------- 1 | # APP 2 | 3 | ## Django app using Apixu 4 | 5 | ### Requirements 6 | * Python 3.5+ (if other version required, adjust [requirements.txt](./requirements.txt)) 7 | * pip 8 | 9 | ### Setup app 10 | 11 | Set SECRET_KEY and APIXUKEY in the .env file. 12 | ``` 13 | cp .env.dist .env 14 | ``` 15 | 16 | Set Apixu library dependency in [requirements.txt](./requirements.txt). 17 | 18 | ### Install dependencies 19 | ``` 20 | pip install -r requirements.txt 21 | ``` 22 | 23 | ### Run app 24 | ``` 25 | python manage.py runserver 26 | ``` 27 | 28 | ### Test 29 | ``` 30 | curl "127.0.0.1:8000/weather?q=London" 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/django/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apixu/apixu-python/370216999346d5caf7f8dc6724b5766dcc6da25d/examples/django/api/__init__.py -------------------------------------------------------------------------------- /examples/django/api/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for api project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = os.environ['SECRET_KEY'] 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'weather' 42 | ] 43 | 44 | REST_FRAMEWORK = { 45 | 'EXCEPTION_HANDLER': 'weather.exception.handler' 46 | } 47 | 48 | MIDDLEWARE = [ 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | 58 | ROOT_URLCONF = 'api.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'api.wsgi.application' 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 81 | 82 | DATABASES = { 83 | 'default': { 84 | 'ENGINE': 'django.db.backends.sqlite3', 85 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 86 | } 87 | } 88 | 89 | 90 | # Password validation 91 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 92 | 93 | AUTH_PASSWORD_VALIDATORS = [ 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 105 | }, 106 | ] 107 | 108 | 109 | # Internationalization 110 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 111 | 112 | LANGUAGE_CODE = 'en-us' 113 | 114 | TIME_ZONE = 'UTC' 115 | 116 | USE_I18N = True 117 | 118 | USE_L10N = True 119 | 120 | USE_TZ = True 121 | 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 125 | 126 | STATIC_URL = '/static/' 127 | -------------------------------------------------------------------------------- /examples/django/api/urls.py: -------------------------------------------------------------------------------- 1 | """api URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('weather.urls')) 22 | ] 23 | -------------------------------------------------------------------------------- /examples/django/api/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for api project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /examples/django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import dotenv 5 | 6 | if __name__ == '__main__': 7 | dotenv.read_dotenv(override=True) 8 | 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | )(exc) 18 | execute_from_command_line(sys.argv) 19 | -------------------------------------------------------------------------------- /examples/django/requirements.txt: -------------------------------------------------------------------------------- 1 | django==2.1.5 2 | djangorestframework==3.9.1 3 | django-dotenv==1.4.2 4 | https://github.com/apixu/apixu-python/archive/vX.X.X.zip 5 | -------------------------------------------------------------------------------- /examples/django/weather/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apixu/apixu-python/370216999346d5caf7f8dc6724b5766dcc6da25d/examples/django/weather/__init__.py -------------------------------------------------------------------------------- /examples/django/weather/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /examples/django/weather/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WeatherConfig(AppConfig): 5 | name = 'weather' 6 | -------------------------------------------------------------------------------- /examples/django/weather/exception.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.response import Response 3 | from apixu.client import ApixuException 4 | from apixu import errors 5 | import logging 6 | 7 | 8 | def handler(exc, context): 9 | message = 'Internal Server Error' 10 | code = status.HTTP_500_INTERNAL_SERVER_ERROR 11 | 12 | if isinstance(exc, ApixuException): 13 | if exc.code == errors.NO_LOCATION_FOUND_FOR_QUERY: 14 | message = 'Could not find location.' 15 | code = status.HTTP_404_NOT_FOUND 16 | logging.exception(exc) 17 | 18 | if isinstance(exc, ValueError): 19 | message = str(exc) 20 | code = status.HTTP_400_BAD_REQUEST 21 | 22 | return Response({'error': message}, status=code) 23 | -------------------------------------------------------------------------------- /examples/django/weather/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apixu/apixu-python/370216999346d5caf7f8dc6724b5766dcc6da25d/examples/django/weather/migrations/__init__.py -------------------------------------------------------------------------------- /examples/django/weather/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /examples/django/weather/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import CurrentWeatherView 3 | import os 4 | from apixu.client import ApixuClient 5 | 6 | api_key = os.environ['APIXUKEY'] 7 | client = ApixuClient(api_key) 8 | 9 | urlpatterns = [ 10 | path('weather', CurrentWeatherView.as_view(apixu=client), name="current-weather") 11 | ] 12 | -------------------------------------------------------------------------------- /examples/django/weather/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.response import Response 2 | from rest_framework.views import APIView 3 | 4 | 5 | class CurrentWeatherView(APIView): 6 | apixu = None 7 | 8 | def get(self, request): 9 | query = request.query_params.get('q', None) 10 | if query is None or query.strip() == '': 11 | raise ValueError('Empty or missing query.') 12 | 13 | return Response(self.apixu.current(q=query)) 14 | -------------------------------------------------------------------------------- /examples/flask/.env.dist: -------------------------------------------------------------------------------- 1 | APIXUKEY= -------------------------------------------------------------------------------- /examples/flask/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | *.egg-info/ 4 | build/ 5 | dist/ 6 | *.egg 7 | .cache 8 | .pytest_cache/ 9 | -------------------------------------------------------------------------------- /examples/flask/README.md: -------------------------------------------------------------------------------- 1 | # APP 2 | 3 | ## Basic app using Apixu 4 | 5 | ### Requirements 6 | * Python 7 | * pip 8 | 9 | ### Setup app 10 | 11 | Set APIXUKEY in the .env file. 12 | ``` 13 | cp .env.dist .env 14 | ``` 15 | 16 | Set Apixu library dependency in [requirements.txt](./requirements.txt). 17 | 18 | ### Install dependencies 19 | ``` 20 | pip install -r requirements.txt 21 | ``` 22 | 23 | ### Run app 24 | ``` 25 | python app.py 26 | ``` 27 | -------------------------------------------------------------------------------- /examples/flask/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import dotenv_values 3 | import logging 4 | from apixu.client import ApixuClient, ApixuException 5 | from flask import Flask, request, jsonify 6 | 7 | os.environ.update(dotenv_values()) 8 | 9 | api_key = os.environ['APIXUKEY'] 10 | client = ApixuClient(api_key) 11 | 12 | app = Flask(__name__) 13 | 14 | 15 | @app.errorhandler(Exception) 16 | def handle_invalid_usage(error): 17 | logging.exception(error) 18 | return jsonify({'error': 'Internal Server Error'}), 500 19 | 20 | 21 | @app.errorhandler(ValueError) 22 | def handle_invalid_usage(error): 23 | return jsonify({'error': str(error)}), 400 24 | 25 | 26 | @app.errorhandler(ApixuException) 27 | def handle_invalid_usage(error): 28 | if error.code != 1006: 29 | raise Exception(error) 30 | 31 | return jsonify({'error': 'Could not find location.'}), 404 32 | 33 | 34 | @app.route('/weather', methods=['GET']) 35 | def weather(): 36 | query = request.args.get('q') 37 | if query is None or query.strip() == '': 38 | raise ValueError('Empty or missing query.') 39 | 40 | current = client.current(q=query) 41 | return jsonify(current) 42 | 43 | 44 | if __name__ == '__main__': 45 | app.run() 46 | -------------------------------------------------------------------------------- /examples/flask/requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv==0.10.1 2 | flask==1.0 3 | https://github.com/apixu/apixu-python/archive/vX.X.X.zip 4 | -------------------------------------------------------------------------------- /examples/forecast.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from apixu.client import ApixuClient 4 | 5 | api_key = os.environ['APIXUKEY'] 6 | client = ApixuClient(api_key=api_key, lang="es") 7 | 8 | forecast = client.forecast(q='London', days=2) 9 | 10 | print(forecast['location']['name']) 11 | 12 | print(forecast['current']['last_updated_epoch']) 13 | print(forecast['current']['condition']['text']) 14 | 15 | for day in forecast['forecast']['forecastday']: 16 | print(day['date']) 17 | print(day['day']['maxtemp_c']) 18 | 19 | ''' 20 | { 21 | "location":{ 22 | "name":"London", 23 | "region":"City of London, Greater London", 24 | "country":"United Kingdom", 25 | "lat":51.52, 26 | "lon":-0.11, 27 | "tz_id":"Europe/London", 28 | "localtime_epoch":1548103480, 29 | "localtime":"2019-01-21 20:44" 30 | }, 31 | "current":{ 32 | "last_updated_epoch":1548102624, 33 | "last_updated":"2019-01-21 20:30", 34 | "temp_c":4.0, 35 | "temp_f":39.2, 36 | "is_day":0, 37 | "condition":{ 38 | "text":"Clear", 39 | "icon":"//cdn.apixu.com/weather/64x64/night/113.png", 40 | "code":1000 41 | }, 42 | "wind_mph":6.9, 43 | "wind_kph":11.2, 44 | "wind_degree":210, 45 | "wind_dir":"SSW", 46 | "pressure_mb":1015.0, 47 | "pressure_in":30.4, 48 | "precip_mm":0.0, 49 | "precip_in":0.0, 50 | "humidity":81, 51 | "cloud":0, 52 | "feelslike_c":1.2, 53 | "feelslike_f":34.2, 54 | "vis_km":10.0, 55 | "vis_miles":6.0, 56 | "uv":0.0 57 | }, 58 | "forecast":{ 59 | "forecastday":[ 60 | { 61 | "date":"2019-01-21", 62 | "date_epoch":1548028800, 63 | "day":{ 64 | "maxtemp_c":5.1, 65 | "maxtemp_f":41.2, 66 | "mintemp_c":-1.4, 67 | "mintemp_f":29.5, 68 | "avgtemp_c":2.5, 69 | "avgtemp_f":36.5, 70 | "maxwind_mph":8.9, 71 | "maxwind_kph":14.4, 72 | "totalprecip_mm":0.0, 73 | "totalprecip_in":0.0, 74 | "avgvis_km":18.7, 75 | "avgvis_miles":11.0, 76 | "avghumidity":76.0, 77 | "condition":{ 78 | "text":"Partly cloudy", 79 | "icon":"//cdn.apixu.com/weather/64x64/day/116.png", 80 | "code":1003 81 | }, 82 | "uv":0.7 83 | }, 84 | "astro":{ 85 | "sunrise":"07:54 AM", 86 | "sunset":"04:30 PM", 87 | "moonrise":"04:58 PM", 88 | "moonset":"08:08 AM" 89 | } 90 | } 91 | ] 92 | } 93 | } 94 | ''' 95 | -------------------------------------------------------------------------------- /examples/history.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | 4 | from apixu.client import ApixuClient 5 | 6 | api_key = os.environ['APIXUKEY'] 7 | client = ApixuClient(api_key) 8 | 9 | now = datetime.datetime.now() 10 | history = client.history(q='London', since=datetime.date(now.year, now.month, now.day)) 11 | 12 | print(history['location']['name']) 13 | 14 | for day in history['forecast']['forecastday']: 15 | print(day['date']) 16 | print(day['day']['maxtemp_c']) 17 | print(day['day']['condition']['text']) 18 | 19 | ''' 20 | { 21 | "location":{ 22 | "name":"London", 23 | "region":"City of London, Greater London", 24 | "country":"United Kingdom", 25 | "lat":51.52, 26 | "lon":-0.11, 27 | "tz_id":"Europe/London", 28 | "localtime_epoch":1548103791, 29 | "localtime":"2019-01-21 20:49" 30 | }, 31 | "forecast":{ 32 | "forecastday":[ 33 | { 34 | "date":"2019-01-21", 35 | "date_epoch":1548028800, 36 | "day":{ 37 | "maxtemp_c":5.2, 38 | "maxtemp_f":41.4, 39 | "mintemp_c":1.3, 40 | "mintemp_f":34.3, 41 | "avgtemp_c":3.3, 42 | "avgtemp_f":37.9, 43 | "maxwind_mph":10.3, 44 | "maxwind_kph":16.6, 45 | "totalprecip_mm":0.0, 46 | "totalprecip_in":0.0, 47 | "avgvis_km":20.0, 48 | "avgvis_miles":12.0, 49 | "avghumidity":80.0, 50 | "condition":{ 51 | "text":"Overcast", 52 | "icon":"//cdn.apixu.com/weather/64x64/day/122.png", 53 | "code":1009 54 | }, 55 | "uv":0.0 56 | }, 57 | "astro":{ 58 | "sunrise":"07:54 AM", 59 | "sunset":"04:30 PM", 60 | "moonrise":"04:58 PM", 61 | "moonset":"08:08 AM", 62 | "moon_phase":"Full Moon", 63 | "moon_illumination":"98" 64 | }, 65 | "hour":[ 66 | { 67 | "time_epoch":1548028800, 68 | "time":"2019-01-21 00:00", 69 | "temp_c":3.2, 70 | "temp_f":37.8, 71 | "is_day":0, 72 | "condition":{ 73 | "text":"Partly cloudy", 74 | "icon":"//cdn.apixu.com/weather/64x64/night/116.png", 75 | "code":1003 76 | }, 77 | "wind_mph":2.5, 78 | "wind_kph":4.0, 79 | "wind_degree":355, 80 | "wind_dir":"N", 81 | "pressure_mb":1024.0, 82 | "pressure_in":30.7, 83 | "precip_mm":0.0, 84 | "precip_in":0.0, 85 | "humidity":70, 86 | "cloud":7, 87 | "feelslike_c":2.5, 88 | "feelslike_f":36.5, 89 | "windchill_c":2.5, 90 | "windchill_f":36.5, 91 | "heatindex_c":3.2, 92 | "heatindex_f":37.8, 93 | "dewpoint_c":-1.7, 94 | "dewpoint_f":28.9, 95 | "will_it_rain":0, 96 | "chance_of_rain":"0", 97 | "will_it_snow":0, 98 | "chance_of_snow":"0", 99 | "vis_km":20.0, 100 | "vis_miles":12.0 101 | } 102 | ] 103 | } 104 | ] 105 | } 106 | } 107 | ''' 108 | -------------------------------------------------------------------------------- /examples/search.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from apixu.client import ApixuClient 4 | 5 | api_key = os.environ['APIXUKEY'] 6 | client = ApixuClient(api_key) 7 | 8 | search = client.search(q='London') 9 | 10 | for location in search: 11 | print(location['id']) 12 | print(location['name']) 13 | print(location['region']) 14 | print('\n') 15 | 16 | ''' 17 | [ 18 | { 19 | "id":2801268, 20 | "name":"London, City of London, Greater London, United Kingdom", 21 | "region":"City of London, Greater London", 22 | "country":"United Kingdom", 23 | "lat":51.52, 24 | "lon":-0.11, 25 | "url":"london-city-of-london-greater-london-united-kingdom" 26 | } 27 | ] 28 | ''' 29 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==4.2 2 | jsonschema==2.6 3 | pylint==1.9 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.21 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | __version__ = '0.3.0' 4 | 5 | with open('requirements.txt') as f: 6 | required = f.read().splitlines() 7 | 8 | setup( 9 | name='apixu', 10 | version=__version__, 11 | description='Python library for Apixu Weather API', 12 | author='Andrei Avram', 13 | author_email='avramandrei@ymail.com', 14 | url='https://www.apixu.com/', 15 | packages=find_packages(), 16 | include_package_data=True, 17 | install_requires=required, 18 | classifiers=['Development Status :: 1 - Production/Beta', 19 | 'Environment :: Web Environment', 20 | 'Intended Audience :: Developers', 21 | 'Operating System :: OS Independent', 22 | 'Topic :: Internet :: WWW/HTTP', 23 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 24 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', ], 26 | ) 27 | --------------------------------------------------------------------------------