Skip to content

Commit

Permalink
WIP: Updates to CI config & general project config
Browse files Browse the repository at this point in the history
Deployments
==============================
* Update Travis CI config to automatically build & deploy packages to PyPi for tagged releases
    * Note: using an encrypted token under my PyPi account for deployment (which is what Travis recommends)
* Update 'Infrastructure' section and merge into 'Contributing' section

Test Coverage
==============================
* Register pyinaturalist on coveralls: https://coveralls.io/github/niconoe/pyinaturalist
* Add coverage report using `pytest-cov`
* Send test coverage results to coveralls
* Add a shiny new coveralls badge

Other CI Config
==============================
* Rename `flake8` check from `style` to `lint`
* Add `black --check` to `style` env
* Run `style` env in Travis CI after tests
* Run `mypy` env in Travis CI after tests
* Fix a couple type annotation issues reported by mypi (oops!)
* Update `3.8-dev` env to `3.8`
* Run `coverage`, `style`, `mypy`, and `coveralls` only once, in a separate CI job under py3.8

General Config
==============================
* Require `setuptools`
* Use `setuptools.find_packages()`
* Import existing `__version__` to specify `setuptools` version
* Move static project metadata from `setup.py` to `setup.cfg`
* Let's call the development stage 'Alpha' now instead of 'Pre-Alpha'
* Remove python 3.3 from classifiers (not tested/supported)
* Move `MOCK_RESPONSE` out of `constants.py`, which is imported by
  `__init__.py`. Otherwise, the external import (`requests`) would cause, for example,
  `python setup.py --version` to fail outside a virtualenv
  • Loading branch information
JWCook committed Jun 9, 2020
1 parent 4fb3448 commit 37bf1ff
Show file tree
Hide file tree
Showing 20 changed files with 201 additions and 246 deletions.
27 changes: 21 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,28 @@ matrix:
- python: "3.5"
- python: "3.6"
- python: "3.7"
- python: "3.8-dev"
env: TOXENV=py38
- python: "3.8"
- python: "3.8"
env: TOXENV=codequality

# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
# Install dependencies & run tests
install:
- pip install tox-travis

# command to run tests, e.g. python setup.py test
- pip install tox-travis coveralls
script:
- tox
# Pytest coverage + coveralls are run only once (under py3.8)
after_success:
- echo $TOXENV
- if [ "$TOXENV" = "codequality" ]; then coveralls; fi

# Deploy to PyPI on git tags only (and master branch only by default)
deploy:
provider: pypi
user: __token__
password:
secure: s7FY53bvG0e+xS/pVQC+SX/eGnZR7alAqpE3n6HZvGQec0kGCE3VofCpGVUyguUiUGmm027xIIdSq6Gpx+lBhWRGmf36RDUAaBOV2DVCgEdPf8nX8Gv4vinOepXWVTYal7QNXiDZCiryyA2O+/28A4abGTS37w+zUQyM70UgJwI9hO1zC4dj4maDY2WE1XHS0WHuI3+iRYc/48d5Pc58gDuGWB4adZ7lODB81/d6StxVJkFCCcVcDwpOAJPldHywULxhfQDu3+vwfchP0V7bMOd+eBwK799gfXERmuZROqxVRuNSRgd8a+TBOzOL7ckW66xyCBN+PT8sVro3P6ZJ9FE65f+opS+9Nz+nUK4Y7QhRNu8D4aUpwfJW8UrxUVl474Ni5YqdSPaARHzJsRi2H+Ft288mawzctsoV6xY/LUDh9d+p+qFR3BTVwyUnQC9NcrrBJl9CnHlsqMH2BXSP5Hr+UCP0Mnjq8UqBRIxk4WSx+4UmrtDzSAO4q1GA/Zo9SaXUyl2D/TodpkWqhgYJ9SdcyXITIhMISTCOtOAVHs1dzYkwKNf2Y+rvopfFXS037sAQm+k9MxyBpQyYaObZj+HS/k/QVwuIWOncyZOqcSb/DrLGiwEVe2aTSg/7YEFshnp0tswDhsNLv6jT9gRd4cB+cnVv3siZisvPpYIamH0=
distributions: sdist bdist_wheel
skip_cleanup: true
skip_existing: true
on:
tags: true
74 changes: 72 additions & 2 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,80 @@ Contributing
Contributions are welcome, and they are greatly appreciated! Every
little bit helps, and credit will always be given.

You can contribute in many ways:
Contribution Guidelines
-----------------------

Documentation
~~~~~~~~~~~~~

We use `Sphinx <http://www.sphinx-doc.org/en/master/>`_, and the references page is automatically generated thanks to
``sphinx.ext.autodoc`` and ``sphinx_autodoc_typehints`` extensions. All functions / methods / classes should have a
proper docstrings.

To build the docs locally::

$ tox -e docs

To preview::

# MacOS:
$ open docs/_build/index.html
# Linux:
$ xdg-open docs/_build/index.html

`Hosted documentation <https://pyinaturalist.readthedocs.io/>`_ is automatically updated when code gets pushed to
the ``master`` branch.

Any new or changed behavior should be friefly summarized in ``HISTORY.rst``. This isn't needed for internal
changes (tests, other docs, refactoring, etc.).

Testing
~~~~~~~

We use the `pytest framework <https://docs.pytest.org/en/latest/>`_.

To run locally::

$ pytest

It is however always good to run ``tox`` frequently, to run the tests against multiple Python versions, as well as some
style and type annotations checks::

$ tox

Travis-CI is run when code is pushed to GitHub.

Type Annotations
~~~~~~~~~~~~~~~~

All functions / methods should have parameters and return value type annotations.
Those type annotations are checked by MyPy (``tox -e mypy``) and will appear in the documentation.

Pull Requests
~~~~~~~~~~~~~
Here are some general guidelines for submitting a pull request:

- If the changes are trivial, just briefly explain the changes in the PR description.
- Otherwise, please submit an issue describing the proposed change prior to submitting a PR.
- Make sure the code is tested, annotated and documented as described above.
- Submit the PR to be merged into the ``dev`` branch.

Releases
~~~~~~~~
Releases are based on git tags. Travis CI will build and deploy packages to PyPi on tagged commits
on the ``master`` branch.

- Update the version in ``pyinaturalist/__init__.py``
- Update the release notes in ``HISTORY.rst``
- Merge changes into the ``master`` branch
- Push a new tag, e.g.: ``git tag v0.1 0a845cf && git push origin --tags``
- This will trigger a deployment. Verify that this completes successfully and that the new version
can be installed from pypi with ``pip install``


Types of Contributions
----------------------
You can contribute in many ways:

Report Bugs
~~~~~~~~~~~
Expand Down Expand Up @@ -50,4 +120,4 @@ If you are proposing a feature:
* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
are welcome :)
are welcome :)
5 changes: 2 additions & 3 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
.. :changelog:

History
-------

0.10.0 (TBD)
+++++++++++++++++
0.10.0 (2020-06-TBD)
++++++++++++++++++++

* Added more info & examples to README for taxa endpoints
* Added `minify` option to `node_api.get_taxa_autocomplete()`
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pyinaturalist
:target: https://pyinaturalist.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

.. image:: https://coveralls.io/repos/github/niconoe/pyinaturalist/badge.svg?branch=dev
:target: https://coveralls.io/github/niconoe/pyinaturalist?branch=dev


Python client for the `iNaturalist APIs <https://www.inaturalist.org/pages/api+reference>`_.

Status
Expand Down
12 changes: 2 additions & 10 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,7 @@
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
(
"index",
"pyinaturalist.tex",
u"pyinaturalist Documentation",
u"Nicolas Noé",
"manual",
),
("index", "pyinaturalist.tex", u"pyinaturalist Documentation", u"Nicolas Noé", "manual",),
]

# The name of an image file (relative to this directory) to place at the top of
Expand Down Expand Up @@ -224,9 +218,7 @@

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
("index", "pyinaturalist", u"pyinaturalist Documentation", [u"Nicolas Noé"], 1)
]
man_pages = [("index", "pyinaturalist", u"pyinaturalist Documentation", [u"Nicolas Noé"], 1)]

# If true, show URL addresses after external links.
# man_show_urls = False
Expand Down
1 change: 0 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Contents:

reference
contributing
infrastructure
authors
history

Expand Down
56 changes: 0 additions & 56 deletions docs/infrastructure.rst

This file was deleted.

13 changes: 8 additions & 5 deletions pyinaturalist/api_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
from logging import getLogger
from os import getenv
from typing import Dict
from unittest.mock import Mock

import requests

import pyinaturalist
from pyinaturalist.constants import WRITE_HTTP_METHODS, MOCK_RESPONSE
from pyinaturalist.constants import WRITE_HTTP_METHODS
from pyinaturalist.helpers import preprocess_request_params

# Mock response content to return in dry-run mode
MOCK_RESPONSE = Mock(spec=requests.Response)
MOCK_RESPONSE.json.return_value = {"results": ["nodata"]}

logger = getLogger(__name__)


Expand Down Expand Up @@ -74,9 +79,7 @@ def is_dry_run_enabled(method: str) -> bool:
dry_run_enabled = pyinaturalist.DRY_RUN_ENABLED or env_to_bool("DRY_RUN_ENABLED")
if method in WRITE_HTTP_METHODS:
return (
dry_run_enabled
or pyinaturalist.DRY_RUN_WRITE_ONLY
or env_to_bool("DRY_RUN_WRITE_ONLY")
dry_run_enabled or pyinaturalist.DRY_RUN_WRITE_ONLY or env_to_bool("DRY_RUN_WRITE_ONLY")
)
return dry_run_enabled

Expand All @@ -86,7 +89,7 @@ def env_to_bool(environment_variable: str) -> bool:
variations (case, None vs. False, etc.)
"""
env_value = getenv(environment_variable)
return env_value and str(env_value).lower() not in ["false", "none"]
return bool(env_value) and str(env_value).lower() not in ["false", "none"]


def log_request(*args, **kwargs):
Expand Down
6 changes: 0 additions & 6 deletions pyinaturalist/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import requests
from unittest.mock import Mock

INAT_NODE_API_BASE_URL = "https://api.inaturalist.org/v1/"
INAT_BASE_URL = "https://www.inaturalist.org"

Expand All @@ -10,9 +7,6 @@
DRY_RUN_ENABLED = False # Mock all requests, including GET
DRY_RUN_WRITE_ONLY = False # Only mock 'write' requests
WRITE_HTTP_METHODS = ["PATCH", "POST", "PUT", "DELETE"]
# Mock response content to return in dry-run mode
MOCK_RESPONSE = Mock(spec=requests.Response)
MOCK_RESPONSE.json.return_value = {"results": ["nodata"]}

# All request parameters from both Node API and REST (Rails) API that accept date or datetime strings
DATETIME_PARAMS = [
Expand Down
4 changes: 2 additions & 2 deletions pyinaturalist/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import date, datetime
from typing import Dict, Any
from typing import Any, Dict, Optional

from dateutil.parser import parse as parse_timestamp
from dateutil.tz import tzlocal
Expand All @@ -13,7 +13,7 @@ def merge_two_dicts(x, y):
return z


def preprocess_request_params(params: Dict[str, Any]) -> Dict[str, Any]:
def preprocess_request_params(params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""Perform type conversions, sanity checks, etc. on request parameters"""
if not params:
return {}
Expand Down
33 changes: 7 additions & 26 deletions pyinaturalist/node_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ def make_inaturalist_api_get_call(
Returns a requests.Response object
"""
response = get(
urljoin(INAT_NODE_API_BASE_URL, endpoint),
params=params,
user_agent=user_agent,
**kwargs
urljoin(INAT_NODE_API_BASE_URL, endpoint), params=params, user_agent=user_agent, **kwargs
)
return response

Expand Down Expand Up @@ -59,9 +56,7 @@ def get_observations(params: Dict, user_agent: str = None) -> Dict[str, Any]:
Returns the parsed JSON returned by iNaturalist (observations in r['results'], a list of dicts)
"""

r = make_inaturalist_api_get_call(
"observations", params=params, user_agent=user_agent
)
r = make_inaturalist_api_get_call("observations", params=params, user_agent=user_agent)
return r.json()


Expand All @@ -83,12 +78,7 @@ def get_all_observations(params: Dict, user_agent: str = None) -> List[Dict[str,
while True:
iteration_params = merge_two_dicts(
params,
{
"order_by": "id",
"order": "asc",
"per_page": PER_PAGE_RESULTS,
"id_above": id_above,
},
{"order_by": "id", "order": "asc", "per_page": PER_PAGE_RESULTS, "id_above": id_above,},
)

page_obs = get_observations(params=iteration_params, user_agent=user_agent)
Expand All @@ -112,9 +102,7 @@ def get_taxa_by_id(taxon_id: int, user_agent: str = None) -> Dict[str, Any]:
"""
if not is_int(taxon_id):
raise ValueError("Please specify a single integer for the taxon ID")
r = make_inaturalist_api_get_call(
"taxa/{}".format(taxon_id), {}, user_agent=user_agent
)
r = make_inaturalist_api_get_call("taxa/{}".format(taxon_id), {}, user_agent=user_agent)
r.raise_for_status()
return r.json()

Expand Down Expand Up @@ -152,9 +140,7 @@ def get_taxa(
return r.json()


def get_taxa_autocomplete(
user_agent: str = None, minify: bool = False, **params
) -> Dict[str, Any]:
def get_taxa_autocomplete(user_agent: str = None, minify: bool = False, **params) -> Dict[str, Any]:
"""Given a query string, returns taxa with names starting with the search term
See: https://api.inaturalist.org/v1/docs/#!/Taxa/get_taxa_autocomplete
Expand All @@ -175,9 +161,7 @@ def get_taxa_autocomplete(
:returns: A list of dicts containing taxa results
"""
r = make_inaturalist_api_get_call(
"taxa/autocomplete", params, user_agent=user_agent
)
r = make_inaturalist_api_get_call("taxa/autocomplete", params, user_agent=user_agent)
r.raise_for_status()
json_response = r.json()

Expand All @@ -193,10 +177,7 @@ def format_taxon(taxon: Dict) -> str:
# Visually align taxon IDs (< 7 chars) and ranks (< 11 chars)
common = taxon.get("preferred_common_name")
return "{:>8}: {:>12} {}{}".format(
taxon["id"],
taxon["rank"].title(),
taxon["name"],
" ({})".format(common) if common else "",
taxon["id"], taxon["rank"].title(), taxon["name"], " ({})".format(common) if common else "",
)


Expand Down
Loading

0 comments on commit 37bf1ff

Please sign in to comment.