diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c23e6f6..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,43 +0,0 @@ -version: 2.1 - -orbs: - python: circleci/python@1.2 -workflows: - version: 2 - build-test-coverage: - jobs: - - build-and-test: - context: - - petfinder - - coverage_tokens -jobs: - build-and-test: - docker: - - image: cimg/python:3.12 - steps: - - checkout - - python/install-packages: - pkg-manager: pip - pip-dependency-file: test-requirements.txt - - run: - name: Install petpy - command: python setup.py install - - run: - name: Run tests - command: | - export PETPY_PETFINDER_KEY=$PETPY_PETFINDER_KEY - export PETPY_PETFINDER_SECRET_KEY=$PETPY_PETFINDER_SECRET_KEY - echo "printing $PETPY_PETFINDER_KEY" - cd tests/ - pytest - cd ../ - - run: - name: codecov - command: codecov -t $CODECOV_TOKEN - - run: - name: coveralls - command: | - cd tests/ - coverage run --source=petpy -m pytest - cd ../ - coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 86b8b74..fc28ca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,18 @@ Changelog and version changes made with each release. -## Version 2.3.2 +## Version 2.4.0 * Implemented rate-limiting for API calls that could potentially exceed the quotas set by Petfinder (50 calls/second, 1,000/day). An exponential backoff strategy is employed when rates exceed the given limits and tries ten more times before giving up and returning the collected results. -* Access token that is generated on first initialization of Petfinder is now refreshed automatically - after expiring. - +* Access token that is generated on first initialization of Petfinder should now refresh automatically + after expiring. +* Removed the `max_page_warning` that would be displayed when there were fewer total pages + in the Petfinder API results to those requested in the `pages` parameter. +* A `response` key has been added to the returned animal or organization object. If an ID isn't found, + the `response` value will be 404. +* Type annotations have been added to the `Petfinder` methods. ## Version 2.3.1 @@ -25,7 +29,7 @@ Changelog and version changes made with each release. - `special_needs`: Filters results by animals that have special needs * Search filters that can take multiple values in the `animals()` function, including `age`, `gender`, `status`, `animal_type`, `size`, and `coat`, should - now correctly accept both comma-delimited strings, such as `age='baby,'young'` + now correctly accept both comma-delimited strings, such as `age='baby,young'` and lists or tuples. * The required version for `pandas` has been updated to at least version `1.0.0` @@ -37,7 +41,7 @@ Very small maintenance patch to update several `setup.py` settings. There is no * Support for Python 3.5 has been discontinued. * The `animals()` method has been updated to include new parameters provided by Petfinder's `animal` - endpoint. This parameters include: + endpoint. These parameters include: - `good_with_cats`: Boolean for filtering animals that are designated as good with cats. - `good_with_children`: Boolean for filtering animals that are designated as good with children. - `good_with_dogs`: Boolean for filtering animals that are designated as good with dogs. diff --git a/README.md b/README.md index 8dd7785..c85ece3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Petpy - Python Wrapper for the Petfinder API [![Documentation Status](https://readthedocs.org/projects/petpy/badge/?version=latest)](http://petpy.readthedocs.io/en/latest/?badge=latest) -[![CircleCI](https://circleci.com/gh/aschleg/petpy/tree/master.svg?style=svg)](https://circleci.com/gh/aschleg/petpy/tree/master) [![Coverage Status](https://coveralls.io/repos/github/aschleg/petpy/badge.svg?branch=master)](https://coveralls.io/github/aschleg/petpy?branch=master) [![codecov](https://codecov.io/gh/aschleg/petpy/branch/master/graph/badge.svg)](https://codecov.io/gh/aschleg/petpy) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ac2a4c228a9e425ba11af69f7a5c9e51)](https://www.codacy.com/app/aschleg/petpy?utm_source=github.com&utm_medium=referral&utm_content=aschleg/petpy&utm_campaign=Badge_Grade) diff --git a/test-requirements.txt b/test-requirements.txt index ae5dc59..05f7237 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,4 +7,5 @@ pandas>=1.0.0 requests>=2.18.4 ratelimit>=2.2.1 backoff>=1.11.1 -setuptools \ No newline at end of file +setuptools +pytest-recording \ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py index 3daf3a6..ca74402 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,18 +1,20 @@ import os import pytest import vcr -from dotenv import load_dotenv -from pandas import DataFrame +import time +from pandas import DataFrame +from dotenv import load_dotenv from petpy.api import Petfinder + tape = vcr.VCR( cassette_library_dir='tests/cassettes', serializer='json', - record_mode='once' + record_mode='all' ) -#load_dotenv('../.env') +load_dotenv('.env') key = os.environ.get('PETPY_PETFINDER_KEY') secret_key = os.environ.get('PETPY_PETFINDER_SECRET_KEY') @@ -28,19 +30,21 @@ def test_authentication(): def authenticate(): - petf = Petfinder(key=key, secret=secret_key) - - return petf + pf = Petfinder(key=key, secret=secret_key) + return pf -petf = authenticate() +pf = authenticate() @vcr.use_cassette('tests/cassettes/animal_types.yml') def test_animal_types(): - response1 = petf.animal_types() - response2 = petf.animal_types('cat') - response3 = petf.animal_types(['cat', 'dog']) + time.sleep(2) + response1 = pf.animal_types() + time.sleep(2) + response2 = pf.animal_types('cat') + time.sleep(2) + response3 = pf.animal_types(['cat', 'dog']) assert isinstance(response1, dict) assert isinstance(response2, dict) @@ -51,22 +55,30 @@ def test_animal_types(): assert str.lower(response3['types'][1]['name']) == 'dog' with pytest.raises(ValueError): - petf.animal_types(types='elephant') + time.sleep(2) + pf.animal_types(types='elephant') with pytest.raises(ValueError): - petf.animal_types(types=['dragon', 'unicorn']) + time.sleep(2) + pf.animal_types(types=['dragon', 'unicorn']) with pytest.raises(TypeError): - petf.animal_types(types={}) + time.sleep(2) + pf.animal_types(types={}) @vcr.use_cassette('tests/cassettes/breeds.yml') def test_breeds(): - - response1 = petf.breeds() - response1_df = petf.breeds(return_df=True) - response2 = petf.breeds('cat') - response2_df = petf.breeds('cat', return_df=True) - response3 = petf.breeds(['cat', 'dog']) - response3_df = petf.breeds(['cat', 'dog'], return_df=True) + time.sleep(2) + response1 = pf.breeds() + time.sleep(2) + response1_df = pf.breeds(return_df=True) + time.sleep(2) + response2 = pf.breeds('cat') + time.sleep(2) + response2_df = pf.breeds('cat', return_df=True) + time.sleep(2) + response3 = pf.breeds(['cat', 'dog']) + time.sleep(2) + response3_df = pf.breeds(['cat', 'dog'], return_df=True) assert isinstance(response1, dict) assert len(list(set(list(response1['breeds'].keys())).difference(animal_types))) == 0 @@ -81,44 +93,55 @@ def test_breeds(): assert isinstance(response3_df, DataFrame) with pytest.raises(ValueError): - petf.breeds(types='elephant') + time.sleep(2) + pf.breeds(types='elephant') with pytest.raises(ValueError): - petf.breeds(types=['dragon', 'unicorn']) + time.sleep(2) + pf.breeds(types=['dragon', 'unicorn']) with pytest.raises(TypeError): - petf.breeds(types={}) + time.sleep(2) + pf.breeds(types={}) @vcr.use_cassette('tests/cassettes/animals.yml') def test_animals(): - response1 = petf.animals() - response1_df = petf.animals(return_df=True) - response2 = petf.animals(results_per_page=50, pages=3) - response2_df = petf.animals(results_per_page=50, pages=3, return_df=True) + response1 = pf.animals() + time.sleep(2) + response1_df = pf.animals(return_df=True) + time.sleep(2) + response2 = pf.animals(results_per_page=50, pages=3) + time.sleep(2) + response2_df = pf.animals(results_per_page=50, pages=3, return_df=True) animal_ids = [] for i in response1['animals'][0:3]: animal_ids.append(i['id']) - - response3 = petf.animals(animal_id=animal_ids, results_per_page=5) - response3_df = petf.animals(animal_id=animal_ids, return_df=True, results_per_page=5) - - response4 = petf.animals(animal_id=animal_ids[0], results_per_page=5) - response4_df = petf.animals(animal_id=animal_ids[0], return_df=True, results_per_page=5) - - response5 = petf.animals(good_with_children=1, good_with_cats=1, results_per_page=5) - - response6 = petf.animals(before_date='2020-06-30', after_date='2020-01-01', results_per_page=5) - response7 = petf.animals(before_date='2020-06-30 0:0:0', after_date='2020-01-01 12:00:00', results_per_page=5) - - response8 = petf.animals(good_with_dogs=1, results_per_page=5) - response9 = petf.animals(good_with_dogs=1, good_with_cats=1, good_with_children=1, return_df=True, - results_per_page=5) - response10 = petf.animals(special_needs=1, house_trained=1, declawed=1, return_df=True, results_per_page=5) - + time.sleep(2) + response3 = pf.animals(animal_id=animal_ids, results_per_page=5) + time.sleep(2) + response3_df = pf.animals(animal_id=animal_ids, return_df=True, results_per_page=5) + time.sleep(2) + response4 = pf.animals(animal_id=animal_ids[0], results_per_page=5) + time.sleep(2) + response4_df = pf.animals(animal_id=animal_ids[0], return_df=True, results_per_page=5) + time.sleep(2) + response5 = pf.animals(good_with_children=1, good_with_cats=1, results_per_page=5) + time.sleep(2) + response6 = pf.animals(before_date='2020-06-30', after_date='2020-01-01', results_per_page=5) + time.sleep(2) + response7 = pf.animals(before_date='2020-06-30 0:0:0', after_date='2020-01-01 12:00:00', results_per_page=5) + time.sleep(2) + response8 = pf.animals(good_with_dogs=1, results_per_page=5) + time.sleep(2) + response9 = pf.animals(good_with_dogs=1, good_with_cats=1, good_with_children=1, return_df=True, + results_per_page=5) + time.sleep(2) + response10 = pf.animals(special_needs=1, house_trained=1, declawed=1, return_df=True, results_per_page=5) + time.sleep(2) with pytest.raises(ValueError): - petf.animals(after_date='2021-07-02', before_date='2021-07-01', results_per_page=5) - - response11 = petf.animals(status=['found', 'adoptable'], results_per_page=5) + pf.animals(after_date='2021-07-02', before_date='2021-07-01', results_per_page=5) + time.sleep(2) + response11 = pf.animals(status=['found', 'adoptable'], results_per_page=5) assert isinstance(response1, dict) assert len(response1['animals']) == 20 @@ -164,20 +187,19 @@ def test_animals(): @vcr.use_cassette('tests/cassettes/organizations.yml') def test_organizations(): - response1 = petf.organizations() - response1_df = petf.organizations(return_df=True) - response2 = petf.organizations(results_per_page=50, pages=3) - response2_df = petf.organizations(results_per_page=50, pages=3, return_df=True) + response1 = pf.organizations() + response1_df = pf.organizations(return_df=True) + response2 = pf.organizations(results_per_page=50, pages=3) + response2_df = pf.organizations(results_per_page=50, pages=3, return_df=True) org_ids = [] for i in response1['organizations'][0:3]: org_ids.append(i['id']) - - response3 = petf.organizations(organization_id=org_ids) - response3_df = petf.organizations(organization_id=org_ids, return_df=True) - - response4 = petf.organizations(organization_id=org_ids[0]) - response4_df = petf.organizations(organization_id=org_ids[0], return_df=True) + + response3 = pf.organizations(organization_id=org_ids) + response3_df = pf.organizations(organization_id=org_ids, return_df=True) + response4 = pf.organizations(organization_id=org_ids[0]) + response4_df = pf.organizations(organization_id=org_ids[0], return_df=True) assert isinstance(response1, dict) assert len(response1['organizations']) == 20 @@ -216,42 +238,42 @@ def test_check_parameters(): good_with_dogs = 'yes' with pytest.raises(ValueError): - petf.animals(size=size1) + pf.animals(size=size1) with pytest.raises(ValueError): - petf.animals(size=[size1, size2]) + pf.animals(size=[size1, size2]) with pytest.raises(ValueError): - petf.animals(gender=gender1) + pf.animals(gender=gender1) with pytest.raises(ValueError): - petf.animals(gender=[gender1, gender2]) + pf.animals(gender=[gender1, gender2]) with pytest.raises(ValueError): - petf.animals(age=age1) + pf.animals(age=age1) with pytest.raises(ValueError): - petf.animals(age=[age1, age2]) + pf.animals(age=[age1, age2]) with pytest.raises(ValueError): - petf.animals(coat=coat1) + pf.animals(coat=coat1) with pytest.raises(ValueError): - petf.animals(coat=[coat1, coat2]) + pf.animals(coat=[coat1, coat2]) with pytest.raises(ValueError): - petf.animals(status=status) + pf.animals(status=status) with pytest.raises(ValueError): - petf.animals(sort=sort) + pf.animals(sort=sort) with pytest.raises(ValueError): - petf.animals(distance=distance_int) + pf.animals(distance=distance_int) with pytest.raises(ValueError): - petf.animals(distance=distance_str) + pf.animals(distance=distance_str) with pytest.raises(ValueError): - petf.animals(results_per_page=limit_int) + pf.animals(results_per_page=limit_int) with pytest.raises(ValueError): - petf.animals(results_per_page=limit_str) + pf.animals(results_per_page=limit_str) with pytest.raises(ValueError): - petf.animals(declawed=declawed) + pf.animals(declawed=declawed) with pytest.raises(ValueError): - petf.animals(house_trained=house_trained) + pf.animals(house_trained=house_trained) with pytest.raises(ValueError): - petf.animals(special_needs=special_needs) + pf.animals(special_needs=special_needs) with pytest.raises(ValueError): - petf.animals(good_with_cats=good_with_cats) + pf.animals(good_with_cats=good_with_cats) with pytest.raises(ValueError): - petf.animals(good_with_dogs=good_with_dogs) + pf.animals(good_with_dogs=good_with_dogs) with pytest.raises(ValueError): - petf.animals(good_with_children=good_with_children) + pf.animals(good_with_children=good_with_children)