Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
ringsaturn committed Sep 1, 2020
0 parents commit 6e43c6f
Show file tree
Hide file tree
Showing 15 changed files with 1,122 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
exclude = .git,.nox,venv
max-line-length = 90
select = E,F,W,C
ignore=E501,W503,E203
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
venv/
*.egg-info/
.vscode/
dist/
venv
Empty file added .pre-commit-config.yaml
Empty file.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Caiyun Weather API Python SDK

## Install

Python 3.6+ is required.

```sh
pip install cy-weather-api
```

## Usage

### Request Caiyun API

```py
from cy_weather_api import CyWeatherAPIClient

client = CyWeatherAPIClient(token="TAkhjf8d1nlSlspN")
apiResult = client.fetch(lng=101.8551, lat=26.6832, lang="zh_CN", alert=True)
print(apiResult.result.hourly.description)
apiResult = client.fetch(lng=-0.2008, lat=51.5024, lang="en_GB")
print(apiResult.result.hourly.description)
apiResult = client.fetch(lng=73.9808, lat=40.7648, lang="en_US")
print(apiResult.result.hourly.description)
```

Output sample:

```
晴,今天晚间20点钟后转小雨,其后多云
clear weather over the next 24 hours
clear weather, overcast after 20 o'clock this afternoon, followed by cloudy
```

### Use our dataclass models

The default HTTP client is requests, you can other HTTP cient to request API,
and pass the response dict to our models (based on `dataclasss`):

```py
from cy_weather_api import initFromDict

data = {
"status": "ok",
"api_version": "v2.5",
"api_status": "active",
"lang": "en_US",
"unit": "metric",
"tzshift": 28800,
"timezone": "Asia/Shanghai",
"server_time": 1589125757,
"location": [39.888888, 116.674501],
"result": {"forecast_keypoint": "test forecast_keypoint", "primary": 0},
}
apiResult = initFromDict(data)
```
4 changes: 4 additions & 0 deletions cy_weather_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from cyapi.client import CyAPIClient
from cyapi.models import initFromDict

__all__ = ["CyAPIClient", "initFromDict"]
94 changes: 94 additions & 0 deletions cy_weather_api/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from dataclasses import dataclass

import requests
from cyapi.models import initFromDict, CyWeatherAPIResponseHandler

API_BASE = (
"http://api.caiyunapp.com/v2.5/{token}/{lng},{lat}/weather.json?"
"&lang={lang}"
"&unit={unit}"
"&dailysteps={dailysteps}"
"&hourlysteps={hourlysteps}"
)
VALID_UNIT_OPTIONS = ["metric", "metric:v1", "metric:v2", "imperial", "SI"]
VALID_GRANU_OPTIONS = ["realtime", "minutely", "hourly", "daily", "weather"]


@dataclass
class CyWeatherAPIClient:
token: str = "TAkhjf8d1nlSlspN"
session = requests.Session()

def fetch(
self,
lng: float,
lat: float,
lang: str = "en_US",
begin: int = None,
alert: bool = False,
granu: str = None,
unit: str = "metric",
dailysteps: int = 5,
hourlysteps: int = 48,
) -> CyWeatherAPIResponseHandler:

if unit and unit not in VALID_UNIT_OPTIONS:
raise ValueError(
"Invaliad unit, got {}, expect one from {}".format(
unit, VALID_UNIT_OPTIONS
)
)

if granu and granu not in VALID_GRANU_OPTIONS:
raise ValueError(
"Invaliad granu, got {}, expect one from {}".format(
unit, VALID_GRANU_OPTIONS
)
)

if dailysteps < 1 or dailysteps > 15:
raise ValueError("dailysteps in range [1, 15]")

if hourlysteps < 1 or hourlysteps > 360:
raise ValueError("hourlysteps in range [1, 360]")

url = API_BASE.format(
token=self.token,
lng=lng,
lat=lat,
lang=lang,
unit=unit,
hourlysteps=hourlysteps,
dailysteps=dailysteps,
)

if granu:
url += "&granu={}".format(granu)

if begin is not None:
try:
int(begin)
url += "&begin={}".format(begin)
except Exception:
raise ValueError("Invalid begin")

_data = self.session.get(url).json()
if _data["status"] != "ok":
_mess = (
"Calling Caiyun API Errored with {error_mess}, please check token status\n"
"url: {url}\n"
"data: {api_data}\n"
)
mess = _mess.format(url=url, api_data=_data, error_mess=_data.get("error"))
raise ValueError(mess)
return initFromDict(_data)


if __name__ == "__main__":
client = CyWeatherAPIClient(token="TAkhjf8d1nlSlspN")
apiResult = client.fetch(lng=101.8551, lat=26.6832, lang="zh_CN", alert=True)
print(apiResult.result.hourly.description)
apiResult = client.fetch(lng=-0.2008, lat=51.5024, lang="en_GB")
print(apiResult.result.hourly.description)
apiResult = client.fetch(lng=73.9808, lat=40.7648, lang="en_US")
print(apiResult.result.hourly.description)
84 changes: 84 additions & 0 deletions cy_weather_api/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Dataclass for Caiyun Weather API.
Below dataclasses arr based on Caiyun Weather API v2.5, the offical API docs:
zh_CN: https://open.caiyunapp.com/通用预报接口/v2.5
en_US: https://open.caiyunapp.com/General_weather_interface/v2.5
We encourage use Golang's coding style:
1. Use black as format tool.
2. Use Camel-Case.
3. Avoid complex oop tricks.
"""

import json
from dataclasses import asdict, dataclass, is_dataclass
from typing import Dict, List

import orjson
from dacite import from_dict

from cyapi.models.result import cyWeatherAPIResponseResultStruct


class EnhancedJSONEncoder(json.JSONEncoder):
def default(self, o):
if is_dataclass(o):
return asdict(o)
return super().default(o)


@dataclass
class CyWeatherAPIResponseHandler:
status: str
api_version: str
api_status: str
lang: str
unit: str
tzshift: int
timezone: str
server_time: int
location: List[float]
result: cyWeatherAPIResponseResultStruct

def __post_init__(self):
pass

def dumps(self, ensure_text=True) -> bytes:
"""Fast dumps via orjson.
Check orjson docs for details: https://github.com/ijl/orjson#dataclass
"""
if ensure_text:
return orjson.dumps(self).decode("utf-8")
else:
return orjson.dumps(self)

def dumps_pretty(self, **kwargs) -> str:
"""Dumps JSON with indent.
Note: please do not change `cls` option if you don't know it.
"""
return json.dumps(self, cls=EnhancedJSONEncoder, **kwargs)


def initFromDict(data: Dict) -> CyWeatherAPIResponseHandler:
return from_dict(data_class=CyWeatherAPIResponseHandler, data=data)


if __name__ == "__main__":
import requests
# from dacite import from_dict

# NOTE: Test token, no one can ensure its availability.
TOKEN = "TAkhjf8d1nlSlspN"
LNG, LAT = 116.3883, 39.9289
URL = "http://api.caiyunapp.com/v2.5/{TOKEN}/{LNG},{LAT}/weather".format(
TOKEN=TOKEN, LNG=LNG, LAT=LAT
)

apiResponseDataclass = from_dict(
data_class=CyWeatherAPIResponseHandler, data=requests.get(URL).json()
)
print(apiResponseDataclass.dumps())
31 changes: 31 additions & 0 deletions cy_weather_api/models/alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from dataclasses import dataclass
from typing import List


@dataclass
class cyWeatherAPIResponseAlertContentItemStruct:
province: str
status: str
code: str
description: str
pubtimestamp: float
city: str
adcode: str
regionId: str
latlon: List[float]
county: str
alertId: str
request_status: str
source: str
title: str
location: str

def __post__init(self):
self.lng = self.latlon[-1]
self.lat = self.latlon[0]


@dataclass
class cyWeatherAPIResponseAlertStruct:
status: str = None
content: List[cyWeatherAPIResponseAlertContentItemStruct] = None
96 changes: 96 additions & 0 deletions cy_weather_api/models/daily.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from dataclasses import dataclass
from typing import List, Any


@dataclass
class cyWeatherAPIResponseDailyAstroItemTimeStruct:
time: str


@dataclass
class cyWeatherAPIResponseDailyAstroItemStruct:
date: str
sunrise: cyWeatherAPIResponseDailyAstroItemTimeStruct
sunset: cyWeatherAPIResponseDailyAstroItemTimeStruct


@dataclass
class cyWeatherAPIResponseDailyMaxMinAvgItemStruct:
date: str
max: float
min: float
avg: float


@dataclass
class cyWeatherAPIResponseDailyWindItemPropertyStruct:
speed: float
direction: float


@dataclass
class cyWeatherAPIResponseDailyWindItemStruct:
date: str
max: cyWeatherAPIResponseDailyWindItemPropertyStruct
min: cyWeatherAPIResponseDailyWindItemPropertyStruct
avg: cyWeatherAPIResponseDailyWindItemPropertyStruct


@dataclass
class cyWeatherAPIResponseDailyAirQualityItemAQITypeStruct:
chn: float
usa: float


@dataclass
class cyWeatherAPIResponseDailyAirQualityItemAQIStruct:
date: str
max: cyWeatherAPIResponseDailyAirQualityItemAQITypeStruct
min: cyWeatherAPIResponseDailyAirQualityItemAQITypeStruct
avg: cyWeatherAPIResponseDailyAirQualityItemAQITypeStruct


@dataclass
class cyWeatherAPIResponseDailyAirQualityItemStruct:
aqi: List[cyWeatherAPIResponseDailyAirQualityItemAQIStruct]
pm25: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]


@dataclass
class cyWeatherAPIResponseDailySkyconItemStruct:
date: str
value: str


@dataclass
class cyWeatherAPIResponseDailyDateIndexDescItemStruct:
date: str
index: Any
desc: str


@dataclass
class cyWeatherAPIResponseDailyLifeIndexItemStruct:
ultraviolet: List[cyWeatherAPIResponseDailyDateIndexDescItemStruct]
carWashing: List[cyWeatherAPIResponseDailyDateIndexDescItemStruct]
dressing: List[cyWeatherAPIResponseDailyDateIndexDescItemStruct]
comfort: List[cyWeatherAPIResponseDailyDateIndexDescItemStruct]
coldRisk: List[cyWeatherAPIResponseDailyDateIndexDescItemStruct]


@dataclass
class cyWeatherAPIResponseDailyStruct:
status: str
astro: List[cyWeatherAPIResponseDailyAstroItemStruct]
precipitation: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
temperature: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
humidity: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
cloudrate: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
pressure: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
visibility: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
dswrf: List[cyWeatherAPIResponseDailyMaxMinAvgItemStruct]
wind: List[cyWeatherAPIResponseDailyWindItemStruct]
skycon: List[cyWeatherAPIResponseDailySkyconItemStruct]
skycon_08h_20h: List[cyWeatherAPIResponseDailySkyconItemStruct]
skycon_20h_32h: List[cyWeatherAPIResponseDailySkyconItemStruct]
life_index: cyWeatherAPIResponseDailyLifeIndexItemStruct
Loading

0 comments on commit 6e43c6f

Please sign in to comment.