为了更好的解决这个问题,假设你正在创建一个天气状况的应用。这个应用使用第三方天气状况 REST API 来检索一个城市的天气信息。其中一个需求是生成一个简单的 HTML 页面,像下面这个图片:
伦敦的天气,OpenWeatherMap。图片是作者自己制作的。
为了获得天气的信息,必须得去某个地方找。幸运的是,通过 OpenWeatherMap 的 REST API 服务,可以获得一切需要的信息。
好的,很棒,但是我该怎么用呢?
通过发送一个 GET 请求到:https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}&units=metric,就可以获得你所需要的所有东西。在这个教程中,我会把城市名字设置成一个参数,并确定使用公制单位。
deffind_weather_for(city: str) -> dict: """Queries the weather API and returns the weather data for a particular city.""" url = API.format(city_name=city, api_key=API_KEY) resp = requests.get(url) return resp.json()
这个 URL 是由两个全局变量构成:
1 2 3
BASE_URL = "https://api.openweathermap.org/data/2.5/weather" API = BASE_URL + "?q={city_name}&appid={api_key}&units=metric"
现在来创建一个叫做 retrieve_weather 的函数。使用这个函数调用 API,然后返回一个 WeatherInfo,这样就可创建你自己的 HTML 页面。
1 2 3 4 5
defretrieve_weather(city: str) -> WeatherInfo: """Finds the weather for a city and returns a WeatherInfo instance.""" data = find_weather_for(city) return WeatherInfo.from_dict(data)
@pytest.fixture() deffake_weather_info(): """Fixture that returns a static weather data.""" withopen("tests/resources/weather.json") as f: return json.load(f)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
deftest_retrieve_weather_using_mocks(mocker, fake_weather_info): """Given a city name, test that a HTML report about the weather is generated correctly.""" # Creates a fake requests response object fake_resp = mocker.Mock() # Mock the json method to return the static weather data fake_resp.json = mocker.Mock(return_value=fake_weather_info) # Mock the status code fake_resp.status_code = HTTPStatus.OK
@responses.activate deftest_retrieve_weather_using_responses(fake_weather_info): """Given a city name, test that a HTML report about the weather is generated correctly.""" api_uri = API.format(city_name="London", api_key=API_KEY) responses.add(responses.GET, api_uri, json=fake_weather_info, status=HTTPStatus.OK)
deffind_weather_for(city: str) -> dict: """Queries the weather API and returns the weather data for a particular city.""" url = API.format(city_name=city, api_key=API_KEY) resp = requests.get(url) return resp.json()
变成这样:
1 2 3 4 5
deffind_weather_for(city: str) -> dict: """Queries the weather API and returns the weather data for a particular city.""" url = API.format(city_name=city, api_key=API_KEY) return adapter(url)
defretrieve_weather(city: str) -> WeatherInfo: """Finds the weather for a city and returns a WeatherInfo instance.""" data = find_weather_for(city, adapter=requests_adapter) return WeatherInfo.from_dict(data)
所以,如果你决定改为使用 urllib 的实现,只要换一下适配器:
1 2 3 4 5 6
defurllib_adapter(url: str) -> dict: """An adapter that encapsulates urllib.urlopen""" with urllib.request.urlopen(url) as response: resp = response.read() return json.loads(resp)
1 2 3 4 5
defretrieve_weather(city: str) -> WeatherInfo: """Finds the weather for a city and returns a WeatherInfo instance.""" data = find_weather_for(city, adapter=urllib_adapter) return WeatherInfo.from_dict(data)