diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..9ca8198 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,39 @@ +name: Publish a package to PyPI + +on: + release: + types: + - created + +env: + VERSION: ${{ github.event.release.tag_name != '' && github.event.release.tag_name || '0.0.0' }} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.x + + - name: Install Dependencies + run: | + pip3 install -r requirements.txt + pip3 install wheel + + - name: Replace version + run: | + sed -i "s/__VERSION__ = '0.0.0'/__VERSION__ = '${{ env.VERSION }}'/" aoirint_jmapy/__init__.py + + - name: Build Package + run: python3 setup.py sdist bdist_wheel + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d2cd6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +.pytest_cache/ +.mypy_cache/ + +/build +/dist +/*.egg-info + +/venv +/testdata diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bb3ec5f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad05b85 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# aoirint_jmapy diff --git a/aoirint_jmapy/__init__.py b/aoirint_jmapy/__init__.py new file mode 100644 index 0000000..08261a4 --- /dev/null +++ b/aoirint_jmapy/__init__.py @@ -0,0 +1,3 @@ +__VERSION__ = '0.0.0' + +from .api import JmaApi diff --git a/aoirint_jmapy/api.py b/aoirint_jmapy/api.py new file mode 100644 index 0000000..389df71 --- /dev/null +++ b/aoirint_jmapy/api.py @@ -0,0 +1,415 @@ +import requests +from typing import List, Dict, Any, Optional, Union, Literal +from pydantic import BaseModel, parse_obj_as + +def get_json(url) -> Dict[str, Any]: + headers = { + 'User-Agent': 'aoirint_jmapy 0.0.0', + } + + res = requests.get(url, headers=headers) + res.raise_for_status() + + data = res.json() + return data + +# Area +class AreaDataCenter(BaseModel): + name: str + enName: str + officeName: str + parent: Optional[str] + children: List[str] + +class AreaDataClass20s(BaseModel): + name: str + enName: str + kana: str + parent: Optional[str] + +class AreaData(BaseModel): + centers: Dict[str, AreaDataCenter] + class20s: Dict[str, AreaDataClass20s] + +# Forecast Area +class ForecastAreaDataValueItem(BaseModel): + class10: str + amedas: List[str] + class20: str + +ForecastAreaData = Dict[str, List[ForecastAreaDataValueItem]] + +# EN Amedas +EnAmedasData = Dict[str, Optional[str]] + +# Anniversary +AnniversaryData = List[str] + +# Weeb Area +class WeekAreaDataItem(BaseModel): + srf: str + week: str + amedas: str + +WeekAreaData = Dict[str, List[WeekAreaDataItem]] + +# Weeb Area 05 +WeekArea05Data = Dict[str, List[str]] + +# Weeb Area Name +class WeekAreaNameDataValue(BaseModel): + jp: str + en: str + +WeekAreaNameData = Dict[str, WeekAreaNameDataValue] + +# Overview Forecast +class OverviewForecastData(BaseModel): + publishingOffice: str + reportDatetime: str + targetArea: str + headlineText: str + text: str + +# Overview Week +class OverviewWeekData(BaseModel): + publishingOffice: str + reportDatetime: str + headTitle: str + text: str + +# Forecast +class ForecastDataTimeSeriesItemArea(BaseModel): + name: str + code: str + +class ForecastDataShortTimeSeriesItemAreaForecast(BaseModel): + area: ForecastDataTimeSeriesItemArea + weatherCodes: List[str] + weathers: List[str] + winds: List[str] + waves: List[str] + +class ForecastDataShortTimeSeriesItemAreaPop(BaseModel): + area: ForecastDataTimeSeriesItemArea + pops: List[str] + +class ForecastDataShortTimeSeriesItemAreaTemp(BaseModel): + area: ForecastDataTimeSeriesItemArea + temps: List[str] + +class ForecastDataShortTimeSeriesItem(BaseModel): + timeDefines: List[str] + areas: List[Union[ForecastDataShortTimeSeriesItemAreaForecast, ForecastDataShortTimeSeriesItemAreaPop, ForecastDataShortTimeSeriesItemAreaTemp]] + +class ForecastDataShort(BaseModel): + publishingOffice: str + reportDatetime: str + timeSeries: List[ForecastDataShortTimeSeriesItem] + +class ForecastDataWeeklyTimeSeriesItemAreaPop(BaseModel): + area: ForecastDataTimeSeriesItemArea + weatherCodes: List[str] + pops: List[str] + reliabilities: List[str] + +class ForecastDataWeeklyTimeSeriesItemAreaTemp(BaseModel): + area: ForecastDataTimeSeriesItemArea + tempsMin: List[str] + tempsMinUpper: List[str] + tempsMinLower: List[str] + tempsMax: List[str] + tempsMaxUpper: List[str] + tempsMaxLower: List[str] + +class ForecastDataWeeklyTimeSeriesItem(BaseModel): + timeDefines: List[str] + areas: List[Union[ForecastDataWeeklyTimeSeriesItemAreaPop, ForecastDataWeeklyTimeSeriesItemAreaTemp]] + +class ForecastDataWeeklyTempAverageArea(BaseModel): + area: ForecastDataTimeSeriesItemArea + min: str + max: str + +class ForecastDataWeeklyTempAverage(BaseModel): + areas: List[ForecastDataWeeklyTempAverageArea] + +class ForecastDataWeeklyPrecipAverageArea(BaseModel): + area: ForecastDataTimeSeriesItemArea + min: str + max: str + +class ForecastDataWeeklyPrecipAverage(BaseModel): + areas: List[ForecastDataWeeklyPrecipAverageArea] + +class ForecastDataWeekly(BaseModel): + publishingOffice: str + reportDatetime: str + timeSeries: List[ForecastDataWeeklyTimeSeriesItem] + tempAverage: ForecastDataWeeklyTempAverage + precipAverage: ForecastDataWeeklyPrecipAverage + +ForecastData = List[Union[ForecastDataShort, ForecastDataWeekly]] + +# Warning +class WarningDataAreaTypeAreaWarning(BaseModel): + code: str + status: str + +class WarningDataAreaTypeArea(BaseModel): + code: str + warnings: List[WarningDataAreaTypeAreaWarning] + +class WarningDataAreaType(BaseModel): + areas: List[WarningDataAreaTypeArea] + +# 雷危険度 +class WarningDataTimeSeriesItemAreaWarningLevelLightningLocalArea(BaseModel): + values: List[str] + additions: List[str] + +class WarningDataTimeSeriesItemAreaWarningLevelLightning(BaseModel): + type: Literal['雷危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningLevelLightningLocalArea] + +class WarningDataTimeSeriesItemAreaWarningLevelLightningContinueLevelLocalArea(BaseModel): + value: str + +class WarningDataTimeSeriesItemAreaWarningLevelLightningContinueLevel(BaseModel): + type: Literal['雷危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningLevelLightningContinueLevelLocalArea] + +# 風危険度 +class WarningDataTimeSeriesItemAreaWarningLevelWindLocalArea(BaseModel): + localAreaName: Optional[str] + values: List[str] + +class WarningDataTimeSeriesItemAreaWarningLevelWind(BaseModel): + type: Literal['風危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningLevelWindLocalArea] + +class WarningDataTimeSeriesItemAreaWarningLevelWindContinueLevelLocalArea(BaseModel): + value: str + +class WarningDataTimeSeriesItemAreaWarningLevelWindContinueLevel(BaseModel): + type: Literal['風危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningLevelWindContinueLevelLocalArea] + +# 波危険度 +class WarningDataTimeSeriesItemAreaWarningLevelWaveLocalArea(BaseModel): + localAreaName: Optional[str] + values: List[str] + +class WarningDataTimeSeriesItemAreaWarningLevelWave(BaseModel): + type: Literal['波危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningLevelWaveLocalArea] + +class WarningDataTimeSeriesItemAreaWarningLevelWaveContinueLevelLocalArea(BaseModel): + value: str + +class WarningDataTimeSeriesItemAreaWarningLevelWaveContinueLevel(BaseModel): + type: Literal['波危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningLevelWaveContinueLevelLocalArea] + +# 風向 +class WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirectionLocalAreaWindDirection(BaseModel): + condition: Optional[str] + value: Optional[str] + +class WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirectionLocalArea(BaseModel): + localAreaName: Optional[str] + windDirections: List[WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirectionLocalAreaWindDirection] + +class WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirection(BaseModel): + type: Literal['風向'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirectionLocalArea] + +# 最大風速 +class WarningDataTimeSeriesItemAreaWarningWindPropertyMaxWindSpeedLocalArea(BaseModel): + localAreaName: Optional[str] + values: List[str] + +class WarningDataTimeSeriesItemAreaWarningWindPropertyMaxWindSpeed(BaseModel): + type: Literal['最大風速'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningWindPropertyMaxWindSpeedLocalArea] + +# 波危険度 +class WarningDataTimeSeriesItemAreaWarningWindPropertyWaveLocalArea(BaseModel): + localAreaName: Optional[str] + values: List[str] + +class WarningDataTimeSeriesItemAreaWarningWindPropertyWave(BaseModel): + type: Literal['波危険度'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningWindPropertyWaveLocalArea] + +# 波高 +class WarningDataTimeSeriesItemAreaWarningWindPropertyWaveHeightLocalArea(BaseModel): + localAreaName: Optional[str] + values: List[str] + +class WarningDataTimeSeriesItemAreaWarningWindPropertyWaveHeight(BaseModel): + type: Literal['波高'] + localAreas: List[WarningDataTimeSeriesItemAreaWarningWindPropertyWaveHeightLocalArea] + +# class WarningDataTimeSeriesItemAreaWarning(BaseModel): +# code: str +# levels: List[Union[WarningDataTimeSeriesItemAreaWarningLevelLightning, WarningDataTimeSeriesItemAreaWarningLevelWind, WarningDataTimeSeriesItemAreaWarningLevelWave]] +# continueLevels: Optional[List[Union[WarningDataTimeSeriesItemAreaWarningLevelLightningContinueLevel, WarningDataTimeSeriesItemAreaWarningLevelWindContinueLevel, WarningDataTimeSeriesItemAreaWarningLevelWaveContinueLevel]]] +# properties: Optional[List[Union[WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirection, WarningDataTimeSeriesItemAreaWarningWindPropertyMaxWindSpeed, WarningDataTimeSeriesItemAreaWarningWindPropertyWave, WarningDataTimeSeriesItemAreaWarningWindPropertyWaveHeight]]] + +class WarningDataTimeSeriesItemAreaWarningLightning(BaseModel): + code: str + levels: List[WarningDataTimeSeriesItemAreaWarningLevelLightning] + continueLevels: Optional[List[WarningDataTimeSeriesItemAreaWarningLevelLightningContinueLevel]] + +class WarningDataTimeSeriesItemAreaWarningWind(BaseModel): + code: str + levels: List[WarningDataTimeSeriesItemAreaWarningLevelWind] + continueLevels: Optional[List[WarningDataTimeSeriesItemAreaWarningLevelWindContinueLevel]] + properties: Optional[List[Union[WarningDataTimeSeriesItemAreaWarningWindPropertyWindDirection, WarningDataTimeSeriesItemAreaWarningWindPropertyMaxWindSpeed]]] + +class WarningDataTimeSeriesItemAreaWarningWave(BaseModel): + code: str + levels: List[WarningDataTimeSeriesItemAreaWarningLevelWave] + properties: Optional[List[Union[WarningDataTimeSeriesItemAreaWarningWindPropertyWaveHeight]]] + +WarningDataTimeSeriesItemAreaWarning = Union[WarningDataTimeSeriesItemAreaWarningWind, WarningDataTimeSeriesItemAreaWarningLightning, WarningDataTimeSeriesItemAreaWarningWave] + +class WarningDataTimeSeriesItemAreaTypeArea(BaseModel): + code: str + warnings: List[WarningDataTimeSeriesItemAreaWarning] + +class WarningDataTimeSeriesItemAreaType(BaseModel): + areas: List[WarningDataTimeSeriesItemAreaTypeArea] + +class WarningDataTimeSeriesItem(BaseModel): + timeDefines: List[str] + areaTypes: List[WarningDataTimeSeriesItemAreaType] + +class WarningData(BaseModel): + publishingOffice: str + reportDatetime: str + headlineText: str + notice: str + areaTypes: List[WarningDataAreaType] + timeSeries: List[WarningDataTimeSeriesItem] + +class JmaApi: + def area(self, + url='https://www.jma.go.jp/bosai/common/const/area.json', + data=None, + ) -> AreaData: + if data is None: + data = get_json(url) + area = parse_obj_as(AreaData, data) + return area + + def forecast_area(self, + url='https://www.jma.go.jp/bosai/forecast/const/forecast_area.json', + data=None, + ) -> ForecastAreaData: + if data is None: + data = get_json(url) + forecast_area_data = parse_obj_as(ForecastAreaData, data) + return forecast_area_data + + def en_amedas(self, + url='https://www.jma.go.jp/bosai/forecast/const/en_amedas.json', + data=None, + ) -> EnAmedasData: + if data is None: + data = get_json(url) + en_amedas_data = parse_obj_as(EnAmedasData, data) + return en_amedas_data + + def anniversary(self, + url='https://www.jma.go.jp/bosai/forecast/const/anniversary.json', + data=None, + ) -> AnniversaryData: + if data is None: + data = get_json(url) + anniversary_data = parse_obj_as(AnniversaryData, data) + return anniversary_data + + def week_area(self, + url='https://www.jma.go.jp/bosai/forecast/const/week_area.json', + data=None, + ) -> WeekAreaData: + if data is None: + data = get_json(url) + week_area_data = parse_obj_as(WeekAreaData, data) + return week_area_data + + def week_area05(self, + url='https://www.jma.go.jp/bosai/forecast/const/week_area05.json', + data=None, + ) -> WeekArea05Data: + if data is None: + data = get_json(url) + week_area05_data = parse_obj_as(WeekArea05Data, data) + return week_area05_data + + def week_area_name(self, + url='https://www.jma.go.jp/bosai/forecast/const/week_area_name.json', + data=None, + ) -> WeekAreaNameData: + if data is None: + data = get_json(url) + week_area_name_data = parse_obj_as(WeekAreaNameData, data) + return week_area_name_data + + def overview_forecast(self, + area_id: str=None, + base_url='https://www.jma.go.jp/bosai/forecast/data/overview_forecast/{area_id}.json', + data=None, + ) -> OverviewForecastData: + if data is None: + assert area_id is not None + url = base_url.format(**{ + 'area_id': area_id, + }) + data = get_json(url) + overview_forecast_data = parse_obj_as(OverviewForecastData, data) + return overview_forecast_data + + def overview_week(self, + area_id: str=None, + base_url='https://www.jma.go.jp/bosai/forecast/data/overview_week/{area_id}.json', + data=None, + ) -> OverviewWeekData: + if data is None: + assert area_id is not None + url = base_url.format(**{ + 'area_id': area_id, + }) + data = get_json(url) + overview_week_data = parse_obj_as(OverviewWeekData, data) + return overview_week_data + + def forecast(self, + area_id: str=None, + base_url='https://www.jma.go.jp/bosai/forecast/data/forecast/{area_id}.json', + data=None, + ) -> ForecastData: + if data is None: + assert area_id is not None + url = base_url.format(**{ + 'area_id': area_id, + }) + data = get_json(url) + forecast_data = parse_obj_as(ForecastData, data) + return forecast_data + + def warning(self, + area_id: str=None, + base_url='https://www.jma.go.jp/bosai/warning/data/warning/{area_id}.json', + data=None, + ) -> WarningData: + if data is None: + assert area_id is not None + url = base_url.format(**{ + 'area_id': area_id, + }) + data = get_json(url) + warning_data = parse_obj_as(WarningData, data) + return warning_data diff --git a/get_testdata.sh b/get_testdata.sh new file mode 100755 index 0000000..9969e25 --- /dev/null +++ b/get_testdata.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +wget https://www.jma.go.jp/bosai/common/const/area.json -O testdata/area.json +wget https://www.jma.go.jp/bosai/forecast/const/forecast_area.json -O testdata/forecast_area.json +wget https://www.jma.go.jp/bosai/forecast/const/en_amedas.json -O testdata/en_amedas.json + +wget https://www.jma.go.jp/bosai/forecast/const/anniversary.json -O testdata/anniversary.json + +wget https://www.jma.go.jp/bosai/forecast/const/week_area.json -O testdata/week_area.json + +wget https://www.jma.go.jp/bosai/forecast/const/week_area05.json -O testdata/week_area05.json +wget https://www.jma.go.jp/bosai/forecast/const/week_area_name.json -O testdata/week_area_name.json + +wget https://www.jma.go.jp/bosai/forecast/data/overview_week/130000.json -O testdata/overview_week.json +wget https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json -O testdata/overview_forecast.json + +wget https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json -O testdata/forecast.json +wget https://www.jma.go.jp/bosai/warning/data/warning/130000.json -O testdata/warning.json diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..ff37064 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +plugins = pydantic.mypy diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..e614a88 --- /dev/null +++ b/requirements.in @@ -0,0 +1,2 @@ +requests +pydantic diff --git a/requirements.test.in b/requirements.test.in new file mode 100644 index 0000000..0649610 --- /dev/null +++ b/requirements.test.in @@ -0,0 +1,7 @@ +-r requirements.in + +pytest +mypy +types-requests +types-docutils +lxml-stubs diff --git a/requirements.test.txt b/requirements.test.txt new file mode 100644 index 0000000..15851b7 --- /dev/null +++ b/requirements.test.txt @@ -0,0 +1,52 @@ +# +# This file is autogenerated by pip-compile with python 3.8 +# To update, run: +# +# pip-compile requirements.test.in +# +attrs==21.4.0 + # via pytest +certifi==2022.5.18.1 + # via requests +charset-normalizer==2.0.12 + # via requests +idna==3.3 + # via requests +iniconfig==1.1.1 + # via pytest +lxml-stubs==0.4.0 + # via -r requirements.test.in +mypy==0.960 + # via -r requirements.test.in +mypy-extensions==0.4.3 + # via mypy +packaging==21.3 + # via pytest +pluggy==1.0.0 + # via pytest +py==1.11.0 + # via pytest +pydantic==1.9.1 + # via -r requirements.in +pyparsing==3.0.9 + # via packaging +pytest==7.1.2 + # via -r requirements.test.in +requests==2.27.1 + # via -r requirements.in +tomli==2.0.1 + # via + # mypy + # pytest +types-docutils==0.18.3 + # via -r requirements.test.in +types-requests==2.27.29 + # via -r requirements.test.in +types-urllib3==1.26.15 + # via types-requests +typing-extensions==4.2.0 + # via + # mypy + # pydantic +urllib3==1.26.9 + # via requests diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8cadf73 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with python 3.8 +# To update, run: +# +# pip-compile +# +certifi==2022.5.18.1 + # via requests +charset-normalizer==2.0.12 + # via requests +idna==3.3 + # via requests +pydantic==1.9.1 + # via -r requirements.in +requests==2.27.1 + # via -r requirements.in +typing-extensions==4.2.0 + # via pydantic +urllib3==1.26.9 + # via requests diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5d4c642 --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +from setuptools import setup, find_packages +import sys +from typing import ( + List, +) + +from aoirint_jmapy import __VERSION__ as VERSION + +install_requires: List[str] = [ + # dependencies like requirements.txt + 'requests', + 'pydantic', + # 'numpy>=1.20.2', # https://pypi.org/project/numpy/ +] + +setup( + name='LIBRARY_NAME', + version=VERSION, # '0.1.0-alpha', # == 0.1.0-alpha0 == 0.1.0a0 + # license='MIT', + + # packages=[ 'PACKAGE_NAME', ], + packages=find_packages(), + include_package_data=True, + + # entry_points = { + # 'console_scripts': [ + # # create `main` function in PACKAGE_NAME/scripts/my_command_module.py + # 'my_command_name = PACKAGE_NAME.scripts.my_command_module:main', + # ], + # }, + + install_requires=install_requires, + + author='aoirint', + author_email='aoirint@gmail.com', + + url='https://github.com/aoirint/aoirint_jmapy', + description='Unofficial JMA weather forecast API client', + + long_description=open('README.md', 'r').read(), + long_description_content_type='text/markdown', + + classifiers=[ + 'Development Status :: 3 - Alpha', + # 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + ], +) diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..5d76bd5 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,51 @@ +import sys +import os +import json + +sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + +from aoirint_jmapy import JmaApi + +def test_area(): + with open('testdata/area.json', 'r', encoding='utf-8') as fp: + JmaApi().area(data=json.load(fp)) + +def test_forecast_area(): + with open('testdata/forecast_area.json', 'r', encoding='utf-8') as fp: + JmaApi().forecast_area(data=json.load(fp)) + +def test_en_amedas(): + with open('testdata/en_amedas.json', 'r', encoding='utf-8') as fp: + JmaApi().en_amedas(data=json.load(fp)) + +def test_anniversary(): + with open('testdata/anniversary.json', 'r', encoding='utf-8') as fp: + JmaApi().anniversary(data=json.load(fp)) + +def test_week_area(): + with open('testdata/week_area.json', 'r', encoding='utf-8') as fp: + JmaApi().week_area(data=json.load(fp)) + +def test_week_area05(): + with open('testdata/week_area05.json', 'r', encoding='utf-8') as fp: + JmaApi().week_area05(data=json.load(fp)) + +def test_week_area_name(): + with open('testdata/week_area_name.json', 'r', encoding='utf-8') as fp: + JmaApi().week_area_name(data=json.load(fp)) + +def test_overview_forecast(): + with open('testdata/overview_forecast.json', 'r', encoding='utf-8') as fp: + JmaApi().overview_forecast(data=json.load(fp)) + +def test_overview_week(): + with open('testdata/overview_week.json', 'r', encoding='utf-8') as fp: + JmaApi().overview_week(data=json.load(fp)) + +def test_forecast(): + with open('testdata/forecast.json', 'r', encoding='utf-8') as fp: + JmaApi().forecast(data=json.load(fp)) + +def test_warning(): + with open('testdata/warning.json', 'r', encoding='utf-8') as fp: + JmaApi().warning(data=json.load(fp))