pyinaturalist 0.19.0.dev2__tar.gz → 0.19.0.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/HISTORY.md +8 -0
  2. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/PKG-INFO +19 -17
  3. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/README.md +3 -3
  4. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/conf.py +1 -1
  5. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/endpoints.md +56 -55
  6. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/user_guide.md +4 -7
  7. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Tutorial_2_Taxa.ipynb +10 -4
  8. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data.py +2 -2
  9. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinat/__init__.py +1 -1
  10. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/__init__.py +2 -2
  11. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/client.py +3 -5
  12. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/constants.py +4 -5
  13. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/__init__.py +3 -2
  14. pyinaturalist-0.19.0.dev2/pyinaturalist/controllers/controlled_term_controller.py → pyinaturalist-0.19.0.dev3/pyinaturalist/controllers/annotation_controller.py +52 -2
  15. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/observation_controller.py +22 -3
  16. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/converters.py +3 -18
  17. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/docs/__init__.py +1 -1
  18. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/docs/signatures.py +1 -0
  19. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/docs/templates.py +2 -1
  20. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/formatters.py +65 -39
  21. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/__init__.py +11 -4
  22. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/base.py +1 -0
  23. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/checklist.py +2 -1
  24. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/conservation_status.py +2 -1
  25. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/controlled_term.py +2 -0
  26. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/identification.py +1 -1
  27. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/observation.py +119 -23
  28. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/taxon.py +221 -65
  29. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/node_api.py +3 -3
  30. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/request_params.py +37 -22
  31. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/rest_api.py +3 -3
  32. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v0/__init__.py +1 -1
  33. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v0/observations.py +4 -2
  34. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/__init__.py +2 -1
  35. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/observations.py +12 -12
  36. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/taxa.py +32 -1
  37. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v2/__init__.py +1 -1
  38. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyproject.toml +29 -15
  39. pyinaturalist-0.19.0.dev3/test/controllers/test_annotation_controller.py +84 -0
  40. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/controllers/test_observation_controller.py +36 -7
  41. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/controllers/test_project_controller.py +1 -1
  42. pyinaturalist-0.19.0.dev3/test/sample_data/get_lifelist_metadata.json +82 -0
  43. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_taxonomy.json +11 -2
  44. pyinaturalist-0.19.0.dev3/test/sample_data/get_observation_taxonomy_by_genus.json +327 -0
  45. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_with_quality_metrics.json +26 -2
  46. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data.py +33 -4
  47. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_compat.py +1 -1
  48. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_converters.py +4 -2
  49. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_formatters.py +53 -11
  50. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_models.py +267 -43
  51. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_request_params.py +14 -6
  52. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_observations.py +16 -11
  53. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_taxa.py +24 -1
  54. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v2/test_observations_v2.py +2 -2
  55. pyinaturalist-0.19.0.dev2/pyinaturalist/models/life_list.py +0 -112
  56. pyinaturalist-0.19.0.dev2/test/controllers/test_controlled_terms_controller.py +0 -49
  57. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/.readthedocs.yml +0 -0
  58. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/CONTRIBUTING.md +0 -0
  59. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/CONTRIBUTORS.md +0 -0
  60. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/LICENSE +0 -0
  61. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docker-compose.yml +0 -0
  62. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/collapsible_container.css +0 -0
  63. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/collapsible_container.js +0 -0
  64. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/colors.css +0 -0
  65. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/favicon.ico +0 -0
  66. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/pyinaturalist_logo.png +0 -0
  67. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/python-logo-green.png +0 -0
  68. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_static/table.css +0 -0
  69. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_templates/autosummary/module.rst_t +0 -0
  70. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_templates/autosummary_core/class.rst +0 -0
  71. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_templates/header.html +0 -0
  72. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_templates/localtoc.html +0 -0
  73. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_templates/module.rst_t +0 -0
  74. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/_templates/package.rst_t +0 -0
  75. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/automodapi.rst +0 -0
  76. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/code_of_conduct.md +0 -0
  77. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/contributing.md +0 -0
  78. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/contributors.md +0 -0
  79. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/examples.md +0 -0
  80. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/history.md +0 -0
  81. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-new-application-complete.png +0 -0
  82. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-new-application.png +0 -0
  83. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-identifiers.png +0 -0
  84. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-observers.png +0 -0
  85. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-page-annotated.odg +0 -0
  86. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-page-annotated.png +0 -0
  87. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-page.png +0 -0
  88. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-search-bar.png +0 -0
  89. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-search-filters.png +0 -0
  90. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-observation-species-counts.png +0 -0
  91. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-taxon-autocomplete.png +0 -0
  92. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-taxon-page.png +0 -0
  93. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/inat-user-login.png +0 -0
  94. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/open-in-vscode.svg +0 -0
  95. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/password_manager_keying.png +0 -0
  96. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pprint_table.png +0 -0
  97. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo.png +0 -0
  98. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo.xcf +0 -0
  99. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_gh_preview.png +0 -0
  100. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_gh_preview.xcf +0 -0
  101. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_hex.png +0 -0
  102. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_hex.xcf +0 -0
  103. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_med.png +0 -0
  104. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_stacked.xcf +0 -0
  105. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_white.png +0 -0
  106. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/pyinaturalist_logo_white.xcf +0 -0
  107. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/python-logo-green.png +0 -0
  108. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/images/python-logo-green.xcf +0 -0
  109. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/index.md +0 -0
  110. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/pyinaturalist.controllers.rst +0 -0
  111. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/pyinaturalist.models.rst +0 -0
  112. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/docs/reference.md +0 -0
  113. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Data Visualizations - Matplotlib.ipynb +0 -0
  114. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Data Visualizations - Regional Activity Report.ipynb +0 -0
  115. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Data Visualizations - Regional Observation Stats.ipynb +0 -0
  116. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Data Visualizations - Seaborn.ipynb +0 -0
  117. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/README.md +0 -0
  118. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Tutorial_1_Observations.ipynb +0 -0
  119. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/Tutorial_3_Data_Visualizations.ipynb +0 -0
  120. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/__init__.py +0 -0
  121. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/combined_activity_stats.png +0 -0
  122. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/histogram_by_iconic_taxon.png +0 -0
  123. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/identifiers_by_month.png +0 -0
  124. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observation_image_grid.png +0 -0
  125. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observation_map.png +0 -0
  126. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observations_by_date_and_time.png +0 -0
  127. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observations_by_month.png +0 -0
  128. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observations_by_quality_grade.png +0 -0
  129. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observations_by_year.png +0 -0
  130. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/observers_by_month.png +0 -0
  131. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/taxa_by_month.png +0 -0
  132. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/total_observations_by_iconic_taxon.png +0 -0
  133. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/images/total_taxa_by_iconic_taxon.png +0 -0
  134. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/observation_photo_metadata.py +0 -0
  135. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/observations_to_gpx.py +0 -0
  136. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/observations_to_tsp.py +0 -0
  137. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data/fips_to_inat_place_ids.json +0 -0
  138. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data/observation_fields.parquet +0 -0
  139. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data/observations.gpx +0 -0
  140. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data/observations.tsp +0 -0
  141. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data/photo_info.html +0 -0
  142. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/examples/sample_data/us_county_fips_codes.csv +0 -0
  143. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/auth.py +0 -0
  144. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/base_controller.py +0 -0
  145. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/place_controller.py +0 -0
  146. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/project_controller.py +0 -0
  147. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/taxon_controller.py +0 -0
  148. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/controllers/user_controller.py +0 -0
  149. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/docs/docstrings.py +0 -0
  150. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/docs/emoji.py +0 -0
  151. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/docs/model_docs.py +0 -0
  152. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/exceptions.py +0 -0
  153. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/lazy_property.py +0 -0
  154. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/media.py +0 -0
  155. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/message.py +0 -0
  156. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/observation_field.py +0 -0
  157. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/place.py +0 -0
  158. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/project.py +0 -0
  159. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/search.py +0 -0
  160. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/models/user.py +0 -0
  161. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/paginator.py +0 -0
  162. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/py.typed +0 -0
  163. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/session.py +0 -0
  164. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v0/observation_fields.py +0 -0
  165. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/controlled_terms.py +0 -0
  166. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/identifications.py +0 -0
  167. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/messages.py +0 -0
  168. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/observation_fields.py +0 -0
  169. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/places.py +0 -0
  170. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/posts.py +0 -0
  171. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/projects.py +0 -0
  172. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/search.py +0 -0
  173. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v1/users.py +0 -0
  174. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/pyinaturalist/v2/observations.py +0 -0
  175. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/__init__.py +0 -0
  176. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/conftest.py +0 -0
  177. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/controllers/test_place_controller.py +0 -0
  178. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/controllers/test_taxon_controller.py +0 -0
  179. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/controllers/test_user_controller.py +0 -0
  180. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/docs/test_docstrings.py +0 -0
  181. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/docs/test_signatures.py +0 -0
  182. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/add_project_observation.json +0 -0
  183. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/create_observation_fail.json +0 -0
  184. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/create_observation_result.json +0 -0
  185. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/create_observation_v1.json +0 -0
  186. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_access_token.json +0 -0
  187. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_access_token_401.json +0 -0
  188. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_all_observation_identifiers_ex_results.json +0 -0
  189. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_all_observation_observers_ex_results.json +0 -0
  190. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_all_observation_species_counts_ex_results.json +0 -0
  191. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_all_observation_species_counts_page1.json +0 -0
  192. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_all_observation_species_counts_page2.json +0 -0
  193. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_controlled_terms.json +0 -0
  194. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_controlled_terms_for_taxon.json +0 -0
  195. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_identifications.json +0 -0
  196. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_identifications.py +0 -0
  197. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_jwt.json +0 -0
  198. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_messages.json +0 -0
  199. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_nonexistent_observation.json +0 -0
  200. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation.json +0 -0
  201. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation.py +0 -0
  202. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_2.json +0 -0
  203. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_fields.py +0 -0
  204. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_fields_page1.json +0 -0
  205. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_fields_page2.json +0 -0
  206. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_day.json +0 -0
  207. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_day.py +0 -0
  208. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_hour.json +0 -0
  209. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_hour.py +0 -0
  210. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_month.json +0 -0
  211. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_month.py +0 -0
  212. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_month_of_year.json +0 -0
  213. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_month_of_year.py +0 -0
  214. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_week.json +0 -0
  215. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_week.py +0 -0
  216. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_week_of_year.json +0 -0
  217. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_histogram_week_of_year.py +0 -0
  218. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_identifiers_ex_results.json +0 -0
  219. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_identifiers_node_page1.json +0 -0
  220. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_identifiers_node_page2.json +0 -0
  221. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_observers_ex_results.json +0 -0
  222. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_observers_node_page1.json +0 -0
  223. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_observers_node_page2.json +0 -0
  224. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_popular_field_values.json +0 -0
  225. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_popular_field_values.py +0 -0
  226. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_species_counts.json +0 -0
  227. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_species_counts.py +0 -0
  228. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_taxon_summary.py +0 -0
  229. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_taxon_summary_conserved.json +0 -0
  230. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_taxon_summary_listed.json +0 -0
  231. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_with_ofvs.json +0 -0
  232. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observation_with_sounds.json +0 -0
  233. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations.atom +0 -0
  234. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations.csv +0 -0
  235. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations.dwc +0 -0
  236. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations.js +0 -0
  237. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations.json +0 -0
  238. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations.kml +0 -0
  239. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_by_id.json +0 -0
  240. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_by_id.py +0 -0
  241. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_node.py +0 -0
  242. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_node_page1.json +0 -0
  243. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_node_page2.json +0 -0
  244. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_v2_full.json +0 -0
  245. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_v2_full.py +0 -0
  246. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_v2_minimal.json +0 -0
  247. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_observations_v2_minimal.py +0 -0
  248. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_places_autocomplete.json +0 -0
  249. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_places_autocomplete.py +0 -0
  250. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_places_by_id.json +0 -0
  251. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_places_by_id.py +0 -0
  252. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_places_nearby.json +0 -0
  253. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_places_nearby.py +0 -0
  254. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_posts_login.json +0 -0
  255. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_posts_project.json +0 -0
  256. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_projects.json +0 -0
  257. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_projects.py +0 -0
  258. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_projects_by_id.json +0 -0
  259. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_projects_by_id.py +0 -0
  260. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_projects_obs_fields.json +0 -0
  261. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_search.json +0 -0
  262. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_search.py +0 -0
  263. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa.json +0 -0
  264. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa.py +0 -0
  265. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_autocomplete.json +0 -0
  266. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_autocomplete.py +0 -0
  267. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_autocomplete_minified.py +0 -0
  268. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_by_id.json +0 -0
  269. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_by_id.py +0 -0
  270. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_by_id_conservation_statuses.json +0 -0
  271. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_map_layers.json +0 -0
  272. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_with_all_names.json +0 -0
  273. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_taxa_with_preferred_place.json +0 -0
  274. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_user_by_id.json +0 -0
  275. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_user_by_id.py +0 -0
  276. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_users_autocomplete.json +0 -0
  277. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/get_users_autocomplete.py +0 -0
  278. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/obs_image.jpg +0 -0
  279. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/obs_sound.wav +0 -0
  280. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/post_observation_photos.json +0 -0
  281. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/post_observation_photos_list.json +0 -0
  282. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/post_observation_sounds.json +0 -0
  283. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/post_observation_sounds_list.json +0 -0
  284. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/post_put_observation_field_value.json +0 -0
  285. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/put_projects.json +0 -0
  286. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/update_observation_result.json +0 -0
  287. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/upload_photos.json +0 -0
  288. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/upload_photos_and_sounds.json +0 -0
  289. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/sample_data/upload_sounds.json +0 -0
  290. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_auth.py +0 -0
  291. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_client.py +0 -0
  292. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_paginator.py +0 -0
  293. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/test_session.py +0 -0
  294. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v0/test_observation_fields_v0.py +0 -0
  295. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v0/test_observations_v0.py +0 -0
  296. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_controlled_terms.py +0 -0
  297. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_identifications.py +0 -0
  298. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_messages.py +0 -0
  299. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_node_api.py +0 -0
  300. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_observation_fields.py +0 -0
  301. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_places.py +0 -0
  302. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_posts.py +0 -0
  303. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_projects.py +0 -0
  304. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_search.py +0 -0
  305. {pyinaturalist-0.19.0.dev2 → pyinaturalist-0.19.0.dev3}/test/v1/test_users.py +0 -0
@@ -15,9 +15,17 @@ Add support for searching observations by observation fields, using a new `obser
15
15
  * `get_observation_species_counts()`
16
16
 
17
17
  ### Models
18
+ * Add `make_tree()` function to build a tree from `Taxon` objects or a `LifeList`
19
+ * Add `pprint_tree()` function to print a taxon tree on the console
20
+ * Add `Taxon.flatten()` method to return a taxon and its descendants as a flat list
18
21
  * Add `Observation.ident_taxon_ids` dynamic property to get all identification taxon IDs (with ancestors)
19
22
  * Add `Observation.cumulative_ids` dynamic property to calculate agreements/total community identifications
23
+ * Add `Application` model for `Observation.application` (for v2 API)
24
+ * Add `Fave` model for `Observation.faves`
25
+ * Add `Flag` model for `Observation.flags`
26
+ * Add `QualityMetric` model for `Observation.quality_metrics`
20
27
  * Add `Sound` model for `Observation.sounds`
28
+ * Add `Vote` model for `Observation.votes`
21
29
  * Fix initialization of `ListedTaxon.place`
22
30
 
23
31
  ## 0.18.0 (2023-02-27)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyinaturalist
3
- Version: 0.19.0.dev2
3
+ Version: 0.19.0.dev3
4
4
  Summary: iNaturalist API client for python
5
5
  Home-page: https://github.com/pyinat/pyinaturalist
6
6
  License: MIT
@@ -24,25 +24,27 @@ Classifier: Typing :: Typed
24
24
  Provides-Extra: all
25
25
  Provides-Extra: docs
26
26
  Requires-Dist: attrs (>=21.2)
27
- Requires-Dist: furo (>=2023.3,<2024.0) ; extra == "docs"
28
- Requires-Dist: ipython (>=7.25.0,<8.0.0) ; extra == "docs"
27
+ Requires-Dist: furo (>=2023.3,<2024.0) ; (python_version >= "3.8") and (extra == "docs")
28
+ Requires-Dist: ipython (>=7.34,<8.0) ; (python_version >= "3.8") and (extra == "docs")
29
29
  Requires-Dist: keyring (>=22.3)
30
- Requires-Dist: linkify-it-py (>=1.0.1,<2.0.0) ; extra == "docs"
31
- Requires-Dist: myst-parser (>=1.0,<2.0) ; extra == "docs"
32
- Requires-Dist: nbsphinx (>=0.9,<0.10) ; extra == "docs"
30
+ Requires-Dist: linkify-it-py (>=2.0,<3.0) ; (python_version >= "3.8") and (extra == "docs")
31
+ Requires-Dist: markdown-it-py (==2.2) ; python_version == "3.7"
32
+ Requires-Dist: markdown-it-py (>=2.2) ; python_version >= "3.8"
33
+ Requires-Dist: myst-parser (>=1.0) ; (python_version >= "3.8") and (extra == "docs")
34
+ Requires-Dist: nbsphinx (>=0.9,<0.10) ; (python_version >= "3.8") and (extra == "docs")
33
35
  Requires-Dist: platformdirs (>=2.6)
34
36
  Requires-Dist: python-dateutil (>=2.0)
35
37
  Requires-Dist: python-forge (>=18.6)
36
- Requires-Dist: requests (>=2.22)
37
- Requires-Dist: requests-cache (>=1.0)
38
+ Requires-Dist: requests (>=2.25)
39
+ Requires-Dist: requests-cache (>=1.1)
38
40
  Requires-Dist: requests-ratelimiter (>=0.3.2)
39
41
  Requires-Dist: rich (>=10.9)
40
- Requires-Dist: sphinx (>=5.2,<6.0) ; extra == "docs"
41
- Requires-Dist: sphinx-autodoc-typehints (>=1.17,<2.0) ; extra == "docs"
42
- Requires-Dist: sphinx-automodapi (>=0.14,<0.15) ; extra == "docs"
43
- Requires-Dist: sphinx-copybutton (>=0.5) ; extra == "docs"
44
- Requires-Dist: sphinx-design (>=0.2.0) ; extra == "docs"
45
- Requires-Dist: sphinxcontrib-apidoc (>=0.3,<0.4) ; extra == "docs"
42
+ Requires-Dist: sphinx (>=6.2,<7.0) ; (python_version >= "3.8") and (extra == "docs")
43
+ Requires-Dist: sphinx-autodoc-typehints (==1.20) ; (python_version >= "3.8") and (extra == "docs")
44
+ Requires-Dist: sphinx-automodapi (>=0.15,<0.16) ; (python_version >= "3.8") and (extra == "docs")
45
+ Requires-Dist: sphinx-copybutton (>=0.5) ; (python_version >= "3.8") and (extra == "docs")
46
+ Requires-Dist: sphinx-design (>=0.4) ; (python_version >= "3.8") and (extra == "docs")
47
+ Requires-Dist: sphinxcontrib-apidoc (>=0.3,<0.4) ; (python_version >= "3.8") and (extra == "docs")
46
48
  Requires-Dist: ujson (>5.0) ; extra == "all"
47
49
  Project-URL: Documentation, https://pyinaturalist.readthedocs.io
48
50
  Project-URL: Repository, https://github.com/pyinat/pyinaturalist
@@ -99,10 +101,10 @@ Many of the most relevant API endpoints are supported, including:
99
101
  * 💬 Messages
100
102
  * 👀 Observations (multiple formats)
101
103
  * 📷 Observation photos + sounds
102
- * 📊 Observation observers, identifiers, histograms, life lists, and species counts
104
+ * 📊 Observation histograms, observers, identifiers, life lists, and species counts
103
105
  * 📍 Places
104
106
  * 👥 Projects
105
- * 🐦Species
107
+ * 🐦 Species
106
108
  * 👤 Users
107
109
 
108
110
  ## Quickstart
@@ -205,7 +207,7 @@ new_observation_id = response[0]['id']
205
207
  We can then [update the observation](https://pyinaturalist.readthedocs.io/en/stable/modules/pyinaturalist.v1.observations.html#pyinaturalist.v1.observations.update_observation) information, photos, or sounds:
206
208
  ```python
207
209
  update_observation(
208
- 17932425,
210
+ new_observation_id,
209
211
  access_token=token,
210
212
  description='updated description !',
211
213
  photos='~/observations/wasp_nest.jpg',
@@ -49,10 +49,10 @@ Many of the most relevant API endpoints are supported, including:
49
49
  * 💬 Messages
50
50
  * 👀 Observations (multiple formats)
51
51
  * 📷 Observation photos + sounds
52
- * 📊 Observation observers, identifiers, histograms, life lists, and species counts
52
+ * 📊 Observation histograms, observers, identifiers, life lists, and species counts
53
53
  * 📍 Places
54
54
  * 👥 Projects
55
- * 🐦Species
55
+ * 🐦 Species
56
56
  * 👤 Users
57
57
 
58
58
  ## Quickstart
@@ -155,7 +155,7 @@ new_observation_id = response[0]['id']
155
155
  We can then [update the observation](https://pyinaturalist.readthedocs.io/en/stable/modules/pyinaturalist.v1.observations.html#pyinaturalist.v1.observations.update_observation) information, photos, or sounds:
156
156
  ```python
157
157
  update_observation(
158
- 17932425,
158
+ new_observation_id,
159
159
  access_token=token,
160
160
  description='updated description !',
161
161
  photos='~/observations/wasp_nest.jpg',
@@ -14,7 +14,7 @@
14
14
  * Some CSS and JS adds collapsible drop-down container
15
15
  * On Readthedocs, CSS and JS is automatically added for a version dropdown
16
16
  """
17
- # flake8: noqa: E402
17
+ # ruff: noqa: E402
18
18
  import sys
19
19
  from importlib.metadata import version as pkg_version
20
20
  from os import makedirs, symlink
@@ -1,79 +1,80 @@
1
1
  (endpoints)=
2
2
  # {fa}`table` Endpoint Summary
3
3
 
4
- Below is a list of iNaturalist API endpoints that have either been added or may be added in the
5
- future, along with their corresponding functions in pyinaturalist.
4
+ Below is a list of iNaturalist API endpoints that have either been added or are likely be added in
5
+ the future, along with their corresponding functions in pyinaturalist.
6
6
 
7
7
  ## v1 API
8
8
  For all available endpoints, see: <http://api.inaturalist.org/v1/docs/>
9
9
 
10
- | Method | Endpoint | Function
11
- | ------ | -------- | --------
10
+ | Method | Endpoint | Function |
11
+ | ------ | ---------------------------------- | ------------------------------------------------------------ |
12
12
  | POST | /annotations |
13
13
  | DELETE | /annotations/{id} |
14
14
  | POST | /comments |
15
15
  | DELETE | /comments/{id} |
16
16
  | PUT | /comments/{id} |
17
- | GET | /controlled_terms | {py:func}`.get_controlled_terms`
18
- | GET | /controlled_terms/for_taxon | {py:func}`.get_controlled_terms`
19
- | GET | /identifications | {py:func}`.get_identifications`
20
- | GET | /identifications/{id} | {py:func}`.get_identifications_by_id`
17
+ | GET | /controlled_terms | {py:func}`.get_controlled_terms` |
18
+ | GET | /controlled_terms/for_taxon | {py:func}`.get_controlled_terms` |
19
+ | GET | /identifications | {py:func}`.get_identifications` |
20
+ | GET | /identifications/{id} | {py:func}`.get_identifications_by_id` |
21
21
  | GET | /identifications/species_counts |
22
22
  | GET | /identifications/identifiers |
23
23
  | GET | /identifications/observers |
24
24
  | GET | /identifications/similar_species |
25
- | GET | /messages | {py:func}`.get_messages`
26
- | GET | /messages/{id} | {py:func}`.get_message_by_id`
27
- | GET | /messages/unread | {py:func}`.get_unread_meassage_count`
28
- | DELETE | /observation_field_values/{id} | {py:func}`.delete_observation_field`
29
- | PUT | /observation_field_values/{id} | {py:func}`.set_observation_field`
30
- | POST | /observation_field_values | {py:func}`.set_observation_field`
31
- | POST | /observation_photos | {py:func}`.upload`
32
- | POST | /observation_sounds | {py:func}`.upload`
33
- | DELETE | /observations/{id} | {py:func}`~pyinaturalist.v1.observations.delete_observation`
34
- | GET | /observations/{id} | {py:func}`.get_observations_by_id`
35
- | PUT | /observations/{id} | {py:func}`~pyinaturalist.v1.observations.update_observation`
36
- | GET | /observations/{id}/taxon_summary | {py:func}`.get_observation_taxon_summary`
37
- | GET | /observations | {py:func}`~pyinaturalist.v1.observations.get_observations`
38
- | POST | /observations | {py:func}`~pyinaturalist.v1.observations.create_observation`
39
- | GET | /observations/histogram | {py:func}`.get_observation_histogram`
40
- | GET | /observations/identifiers | {py:func}`.get_observation_identifiers`
41
- | GET | /observations/observers | {py:func}`.get_observation_observers`
42
- | GET | /observations/popular_field_values | {py:func}`.get_observation_popular_field_values`
43
- | GET | /observations/species_counts | {py:func}`.get_observation_species_counts`
44
- | GET | /observations/taxonomy | {py:func}`.get_observation_taxonomy`
45
- | GET | /places/{id} | {py:func}`.get_places_by_id`
46
- | GET | /places/autocomplete | {py:func}`.get_places_autocomplete`
47
- | GET | /places/nearby | {py:func}`.get_places_nearby`
48
- | GET | /posts | {py:func}`.get_posts`
49
- | GET | /projects | {py:func}`.get_projects`
50
- | GET | /projects/{id} | {py:func}`.get_projects_by_id`
51
- | PUT | /projects/{id} | {py:func}`.update_project`
25
+ | GET | /messages | {py:func}`.get_messages` |
26
+ | GET | /messages/{id} | {py:func}`.get_message_by_id` |
27
+ | GET | /messages/unread | {py:func}`.get_unread_meassage_count` |
28
+ | DELETE | /observation_field_values/{id} | {py:func}`.delete_observation_field` |
29
+ | PUT | /observation_field_values/{id} | {py:func}`.set_observation_field` |
30
+ | POST | /observation_field_values | {py:func}`.set_observation_field` |
31
+ | POST | /observation_photos | {py:func}`.upload` |
32
+ | POST | /observation_sounds | {py:func}`.upload` |
33
+ | DELETE | /observations/{id} | {py:func}`~pyinaturalist.v1.observations.delete_observation` |
34
+ | GET | /observations/{id} | {py:func}`.get_observations_by_id` |
35
+ | PUT | /observations/{id} | {py:func}`~pyinaturalist.v1.observations.update_observation` |
36
+ | GET | /observations/{id}/taxon_summary | {py:func}`.get_observation_taxon_summary` |
37
+ | GET | /observations | {py:func}`~pyinaturalist.v1.observations.get_observations` |
38
+ | POST | /observations | {py:func}`~pyinaturalist.v1.observations.create_observation` |
39
+ | GET | /observations/histogram | {py:func}`.get_observation_histogram` |
40
+ | GET | /observations/identifiers | {py:func}`.get_observation_identifiers` |
41
+ | GET | /observations/observers | {py:func}`.get_observation_observers` |
42
+ | GET | /observations/popular_field_values | {py:func}`.get_observation_popular_field_values` |
43
+ | GET | /observations/species_counts | {py:func}`.get_observation_species_counts` |
44
+ | GET | /observations/taxonomy | {py:func}`.get_observation_taxonomy` |
45
+ | GET | /places/{id} | {py:func}`.get_places_by_id` |
46
+ | GET | /places/autocomplete | {py:func}`.get_places_autocomplete` |
47
+ | GET | /places/nearby | {py:func}`.get_places_nearby` |
48
+ | GET | /posts | {py:func}`.get_posts` |
49
+ | GET | /projects | {py:func}`.get_projects` |
50
+ | GET | /projects/{id} | {py:func}`.get_projects_by_id` |
51
+ | PUT | /projects/{id} | {py:func}`.update_project` |
52
52
  | GET | /projects/{id}/members |
53
53
  | GET | /projects/{id}/subscriptions |
54
- | POST | /projects/{id}/add | {py:func}`.add_project_observation`
55
- | DELETE | /projects/{id}/remove | {py:func}`.delete_project_observation`
54
+ | POST | /projects/{id}/add | {py:func}`.add_project_observation` |
55
+ | DELETE | /projects/{id}/remove | {py:func}`.delete_project_observation` |
56
56
  | GET | /projects/autocomplete |
57
- | GET | /search | {py:func}`.search`
58
- | GET | /taxa | {py:func}`.get_taxa`
59
- | GET | /taxa/{id} | {py:func}`.get_taxa_by_id`
60
- | GET | /taxa/{id}/map_layers | {py:func}`.get_taxa_map_layers`
61
- | GET | /taxa/autocomplete | {py:func}`.get_taxa_autocomplete`
62
- | GET | /users/{id} | {py:func}`.get_user_by_id`
57
+ | GET | /search | {py:func}`.search` |
58
+ | GET | /taxa | {py:func}`.get_taxa` |
59
+ | GET | /taxa/{id} | {py:func}`.get_taxa_by_id` |
60
+ | GET | /taxa/{id}/map_layers | {py:func}`.get_taxa_map_layers` |
61
+ | GET | /taxa/autocomplete | {py:func}`.get_taxa_autocomplete` |
62
+ | GET | /taxa/lifelist_metadata | {py:func}`.get_lifelist_metadata` |
63
+ | GET | /users/{id} | {py:func}`.get_user_by_id` |
63
64
  | GET | /users/{id}/projects |
64
- | GET | /users/autocomplete | {py:func}`.get_users_autocomplete`
65
- | GET | /users/me | {py:func}`.get_current_user`
65
+ | GET | /users/autocomplete | {py:func}`.get_users_autocomplete` |
66
+ | GET | /users/me | {py:func}`.get_current_user` |
66
67
 
67
68
  ## v0 API
68
69
  For all available endpoints, see: <https://www.inaturalist.org/pages/api+reference>
69
70
 
70
- | Method | Endpoint | Function
71
- | ------ | -------- | --------
72
- | GET | /observations | {py:func}`~pyinaturalist.v0.observations.get_observations`
73
- | POST | /observations | {py:func}`~pyinaturalist.v0.observations.create_observation`
74
- | PUT | /observations/{id} | {py:func}`~pyinaturalist.v0.observations.update_observation`
75
- | DELETE | /observations/{id} | {py:func}`~pyinaturalist.v0.observations.delete_observation`
76
- | GET | /observation_fields | {py:func}`.get_observation_fields`
77
- | PUT | /observation_field_values/{id} | {py:func}`.put_observation_field_values`
78
- | POST | /observation_photos | {py:func}`.upload_photos`
79
- | POST | /observation_sounds | {py:func}`.upload_sounds`
71
+ | Method | Endpoint | Function |
72
+ | ------ | ------------------------------ | ------------------------------------------------------------ |
73
+ | GET | /observations | {py:func}`~pyinaturalist.v0.observations.get_observations` |
74
+ | POST | /observations | {py:func}`~pyinaturalist.v0.observations.create_observation` |
75
+ | PUT | /observations/{id} | {py:func}`~pyinaturalist.v0.observations.update_observation` |
76
+ | DELETE | /observations/{id} | {py:func}`~pyinaturalist.v0.observations.delete_observation` |
77
+ | GET | /observation_fields | {py:func}`.get_observation_fields` |
78
+ | PUT | /observation_field_values/{id} | {py:func}`.put_observation_field_values` |
79
+ | POST | /observation_photos | {py:func}`.upload_photos` |
80
+ | POST | /observation_sounds | {py:func}`.upload_sounds` |
@@ -32,13 +32,14 @@ See {ref}`contributing` for details on setup for local development.
32
32
  :::{admonition} Python version compatibility
33
33
  :class: toggle, tip
34
34
 
35
- pyinaturalist currently requires **python 3.6+**. If you need to use an older version
35
+ pyinaturalist currently requires **python 3.7+**. If you need to use an older version
36
36
  of python, here are the last compatible versions of pyinaturalist:
37
37
 
38
38
  * **python 2.7:** pyinaturalist 0.1
39
39
  * **python 3.4:** pyinaturalist 0.10
40
40
  * **python 3.5:** pyinaturalist 0.11
41
- * **python 3.6:** still supported, but expected to be dropped in a future release
41
+ * **python 3.6:** pyinaturalist 0.16
42
+ * **python 3.7:** still supported, but expected to be dropped in a future release
42
43
  :::
43
44
 
44
45
  ## Imports
@@ -58,7 +59,6 @@ and [search URLs](https://forum.inaturalist.org/t/how-to-use-inaturalists-search
58
59
 
59
60
  For example, if you wanted to search observations by user, these three requests are equivalent:
60
61
 
61
-
62
62
  ::::{tab-set}
63
63
  :::{tab-item} search URL
64
64
  ```
@@ -111,12 +111,11 @@ And here is what that same observation looks like in JSON:
111
111
  ### Previewing Responses
112
112
  These responses can contain large amounts of response attributes, making it somewhat cumbersome if you
113
113
  just want to quickly preview results (for example, in a Jupyter notebook). For that purpose, the
114
- {py:func}`~pyinaturalist.formatters.pprint` function is included to format response data as a
114
+ {py:func}`~pyinaturalist.formatters.pprint` function is available to format response data as a
115
115
  condensed, color-highlighted table.
116
116
 
117
117
  **Examples:**
118
118
 
119
-
120
119
  ::::{tab-set}
121
120
  :::{tab-item} Observations
122
121
  ```
@@ -182,7 +181,6 @@ To get all pages of results and combine them into a single response, use `page='
182
181
  Note that this replaces the `get_all_*()` functions from pyinaturalist\<=0.12.
183
182
 
184
183
  (auth)=
185
-
186
184
  ## Authentication
187
185
  For any endpoints that create, update, or delete data, you will need to authenticate using an
188
186
  OAuth2 access token. This requires both your iNaturalist username and password, and separate
@@ -491,7 +489,6 @@ $Env:DRY_RUN_ENABLED="true"
491
489
  :::
492
490
  ::::
493
491
 
494
-
495
492
  ### Dry-run only write requests
496
493
  If you would like to send real `GET` requests but mock out any requests that modify data
497
494
  (`POST`, `PUT`, and `DELETE`), you can use the `DRY_RUN_WRITE_ONLY` variable instead:
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "cells": [
3
3
  {
4
+ "attachments": {},
4
5
  "cell_type": "markdown",
5
6
  "id": "36ffdd0e",
6
7
  "metadata": {},
@@ -36,14 +37,16 @@
36
37
  ]
37
38
  },
38
39
  {
40
+ "attachments": {},
39
41
  "cell_type": "markdown",
40
42
  "id": "3bd30a71",
41
43
  "metadata": {},
42
44
  "source": [
43
45
  "## Basic taxon search\n",
44
- "Let's say you partially remember either a genus or family name that started with **'vespi'**-something.\n",
45
46
  "The [taxa endpoint](https://pyinaturalist.readthedocs.io/en/stable/modules/pyinaturalist.v1.taxa.html#pyinaturalist.v1.taxa.get_taxa)\n",
46
- "can be used to search by name, rank, and several other criteria"
47
+ "can be used to search by name, rank, and several other criteria.\n",
48
+ "Let's say you partially remember either a genus or family name that started with **'vespi'**-something.\n",
49
+ "Use the `q` parameter to search by name:"
47
50
  ]
48
51
  },
49
52
  {
@@ -58,6 +61,7 @@
58
61
  ]
59
62
  },
60
63
  {
64
+ "attachments": {},
61
65
  "cell_type": "markdown",
62
66
  "id": "9dca3b13",
63
67
  "metadata": {},
@@ -187,6 +191,7 @@
187
191
  ]
188
192
  },
189
193
  {
194
+ "attachments": {},
190
195
  "cell_type": "markdown",
191
196
  "id": "4020d25d",
192
197
  "metadata": {},
@@ -238,13 +243,14 @@
238
243
  ]
239
244
  },
240
245
  {
246
+ "attachments": {},
241
247
  "cell_type": "markdown",
242
248
  "id": "b9767ea3",
243
249
  "metadata": {},
244
250
  "source": [
245
251
  "## Searching by parent ID\n",
246
- "Oh, that's right, it was **'Vespidae'**! Now let's find all of its subfamilies using its taxon ID\n",
247
- "from the results above:"
252
+ "Oh, that's right, it was **'Vespidae'**! Now let's find all of its subfamilies.\n",
253
+ "We can run another search by parent ID, using the taxon ID from the results above:"
248
254
  ]
249
255
  },
250
256
  {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python
2
- # flake8: noqa: F401, F403
2
+ # ruff: noqa: E402, F401, F402, F403, F405
3
3
  """Sample response data of every type to experiment with"""
4
4
  import sys
5
5
 
@@ -17,7 +17,7 @@ annotation = Annotation.from_json(j_annotation_1)
17
17
  comment = Comment.from_json(j_comment_1)
18
18
  controlled_term = ControlledTerm.from_json(j_controlled_term_1)
19
19
  identification = Identification.from_json(j_identification_1)
20
- life_list = LifeList.from_json(j_life_list)
20
+ life_list = LifeList.from_json(j_life_list_1)
21
21
  observation = Observation.from_json(j_observation_1)
22
22
  observation_with_ofvs = Observation.from_json(j_observation_3_ofvs)
23
23
  observation_field = ObservationField.from_json(j_obs_field_1)
@@ -1,4 +1,4 @@
1
- # flake8: noqa: F401, F403
1
+ # ruff: noqa: F401, F403
2
2
  """Namespace alias that combines modules from pyinaturalist and pyinaturalist-convert (if installed).
3
3
  This allows abreviated imports like::
4
4
 
@@ -1,11 +1,11 @@
1
- # flake8: noqa: F401, F403
1
+ # ruff: noqa: F401, F403
2
2
  # isort: skip_file
3
3
  __version__ = '0.19.0'
4
4
 
5
5
  from pyinaturalist.auth import get_access_token
6
6
  from pyinaturalist.client import iNatClient
7
7
  from pyinaturalist.constants import *
8
- from pyinaturalist.formatters import enable_logging, format_table, pprint
8
+ from pyinaturalist.formatters import enable_logging, format_table, pprint, pprint_tree
9
9
  from pyinaturalist.models import *
10
10
  from pyinaturalist.paginator import Paginator, IDPaginator, WrapperPaginator
11
11
  from pyinaturalist.request_params import get_interval_ranges
@@ -10,7 +10,7 @@ from requests import Session
10
10
  from pyinaturalist.auth import get_access_token
11
11
  from pyinaturalist.constants import RequestParams
12
12
  from pyinaturalist.controllers import (
13
- ControlledTermController,
13
+ AnnotationController,
14
14
  ObservationController,
15
15
  PlaceController,
16
16
  ProjectController,
@@ -93,7 +93,7 @@ class iNatClient:
93
93
  tokens as needed. Using a keyring instead is recommended, though.
94
94
  default_params: Default request parameters to pass to any applicable API requests
95
95
  dry_run: Just log all requests instead of sending real requests
96
- loop: An event loop to use to run any executors used for async iteration
96
+ loop: An event loop to run any executors used for async iteration
97
97
  session: Session object to use instead of creating a new one
98
98
  kwargs: Keyword arguments for :py:class:`.ClientSession`
99
99
  """
@@ -117,9 +117,7 @@ class iNatClient:
117
117
  self._token_expires = None
118
118
 
119
119
  # Controllers
120
- self.controlled_terms = ControlledTermController(
121
- self
122
- ) #: Interface for controlled term/annotation requests
120
+ self.annotations = AnnotationController(self) #: Interface for annotation requests
123
121
  self.observations = ObservationController(self) #: Interface for observation requests
124
122
  self.places = PlaceController(self) #: Interface for project requests
125
123
  self.projects = ProjectController(self) #: Interface for project requests
@@ -11,6 +11,7 @@ API_V0 = 'https://www.inaturalist.org'
11
11
  API_V1 = 'https://api.inaturalist.org/v1'
12
12
  API_V2 = 'https://api.inaturalist.org/v2'
13
13
  EXPORT_URL = 'https://www.inaturalist.org/observations/export'
14
+ GBIF_TAXON_BASE_URL = 'https://www.gbif.org/species'
14
15
  INAT_BASE_URL = API_V0
15
16
  INAT_REPO = 'https://raw.githubusercontent.com/inaturalist/inaturalist/main'
16
17
  ICONIC_TAXA_BASE_URL = f'{INAT_REPO}/app/assets/images/iconic_taxa'
@@ -103,6 +104,7 @@ ROOT_TAXON_ID = 48460
103
104
 
104
105
  # Taxonomic ranks that can be filtered on, and numeric values for comparison
105
106
  # Source: https://github.com/inaturalist/inaturalist/blob/main/app/models/taxon.rb
107
+ UNRANKED = 90 # Used for comparing unranked taxa
106
108
  RANK_LEVELS = {
107
109
  'infrahybrid': 5,
108
110
  'form': 5,
@@ -138,18 +140,15 @@ RANK_LEVELS = {
138
140
  'subphylum': 57,
139
141
  'phylum': 60,
140
142
  'kingdom': 70,
141
- 'unranked': 90, # Invented to make parent check work (this is null in the db)
143
+ 'unranked': UNRANKED,
142
144
  'stateofmatter': 100,
143
145
  }
144
146
  RANKS = list(RANK_LEVELS.keys())[:-2]
145
147
 
146
- # Simplified subset of ranks that are useful for display
148
+ # The main 7 taxonomic ranks, used for condensed display
147
149
  COMMON_RANKS = [
148
- 'form',
149
- 'variety',
150
150
  'species',
151
151
  'genus',
152
- 'tribe',
153
152
  'family',
154
153
  'order',
155
154
  'class',
@@ -1,7 +1,8 @@
1
1
  """Controller classes for :py:class:`.iNatClient`"""
2
- # flake8: noqa: F401
2
+ # ruff: noqa: F401
3
+ # isort: skip_file
3
4
  from pyinaturalist.controllers.base_controller import BaseController
4
- from pyinaturalist.controllers.controlled_term_controller import ControlledTermController
5
+ from pyinaturalist.controllers.annotation_controller import AnnotationController
5
6
  from pyinaturalist.controllers.observation_controller import ObservationController
6
7
  from pyinaturalist.controllers.place_controller import PlaceController
7
8
  from pyinaturalist.controllers.project_controller import ProjectController
@@ -1,12 +1,14 @@
1
1
  from typing import Dict, List
2
2
 
3
+ from pyinaturalist.constants import API_V2, IntOrStr
3
4
  from pyinaturalist.controllers import BaseController
4
- from pyinaturalist.docs import document_controller_params
5
+ from pyinaturalist.docs import document_common_args, document_controller_params
5
6
  from pyinaturalist.models import Annotation, ControlledTerm
7
+ from pyinaturalist.session import delete, post
6
8
  from pyinaturalist.v1 import get_controlled_terms, get_controlled_terms_for_taxon
7
9
 
8
10
 
9
- class ControlledTermController(BaseController):
11
+ class AnnotationController(BaseController):
10
12
  """:fa:`tag` Controller for ControlledTerm and Annotation requests"""
11
13
 
12
14
  def __init__(self, *args, **kwargs):
@@ -46,3 +48,51 @@ class ControlledTermController(BaseController):
46
48
  annotation.controlled_attribute = term
47
49
  annotation.controlled_value = term.get_value_by_id(annotation.controlled_value.id)
48
50
  return annotations
51
+
52
+ # TODO: Allow passing labels instead of IDs
53
+ @document_common_args
54
+ def create(
55
+ self,
56
+ controlled_attribute_id: int,
57
+ controlled_value_id: int,
58
+ resource_id: IntOrStr,
59
+ resource_type: str = 'Observation',
60
+ **params,
61
+ ) -> Annotation:
62
+ """Create a new annotation on an observation.
63
+
64
+ Args:
65
+ controlled_attribute_id: Annotation attribute ID
66
+ controlled_value_id: Annotation value ID
67
+ resource_id: Observation ID or UUID
68
+ resource_type: Resource type, if something other than an observation
69
+
70
+ Example:
71
+
72
+ Add a 'Plant phenology: Flowering' annotation to an observation:
73
+
74
+ >>> annotation = client.annotations.create(12, 13, 164609837)
75
+
76
+ Returns:
77
+ The newly created Annotation object
78
+ """
79
+ response = post(
80
+ f'{API_V2}/annotations',
81
+ controlled_attribute_id=controlled_attribute_id,
82
+ controlled_value_id=controlled_value_id,
83
+ resource_id=resource_id,
84
+ resource_type=resource_type,
85
+ **params,
86
+ )
87
+ return Annotation.from_json(response.json()['results'][0])
88
+
89
+ def delete(self, uuid: str, **params):
90
+ """Delete an annotation
91
+
92
+ Args:
93
+ uuid: Annotation UUID
94
+
95
+ Returns:
96
+ Nothing; success means the item has been deleted
97
+ """
98
+ delete(f'{API_V2}/annotations/{uuid}', **params)
@@ -25,6 +25,7 @@ from pyinaturalist.request_params import validate_multiple_choice_param
25
25
  from pyinaturalist.v1 import (
26
26
  create_observation,
27
27
  delete_observation,
28
+ get_life_list_metadata,
28
29
  get_observation_histogram,
29
30
  get_observation_identifiers,
30
31
  get_observation_observers,
@@ -75,7 +76,7 @@ class ObservationController(BaseController):
75
76
  _get_observations,
76
77
  Observation,
77
78
  cls=ObservationPaginator,
78
- annotation_callback=self.client.controlled_terms.lookup,
79
+ annotation_callback=self.client.annotations.lookup,
79
80
  **params,
80
81
  )
81
82
 
@@ -89,9 +90,27 @@ class ObservationController(BaseController):
89
90
  response = self.client.request(get_observation_identifiers, **params)
90
91
  return UserCounts.from_json(response)
91
92
 
92
- @document_controller_params(get_observation_taxonomy, add_common_args=False)
93
- def life_list(self, user_id: IntOrStr, **params) -> LifeList:
93
+ @document_controller_params(get_observation_taxonomy)
94
+ def life_list(
95
+ self, user_id: Optional[IntOrStr] = None, locale: Optional[str] = None, **params
96
+ ) -> LifeList:
97
+ """Get taxa from a user's dynamic life list
98
+
99
+ Args:
100
+ user_id: iNaturalist user ID or username
101
+ locale: Locale preference for taxon common names
102
+ """
94
103
  response = self.client.request(get_observation_taxonomy, user_id=user_id, **params)
104
+
105
+ # Add additional metadata to a life list response; requires a user ID
106
+ if user_id:
107
+ metadata = self.client.request(
108
+ get_life_list_metadata, user_id=user_id, locale=locale, **params
109
+ )
110
+ meta_by_id = {item['id']: item for item in metadata['results']}
111
+ for taxon in response['results']:
112
+ taxon.update(meta_by_id.get(taxon['id'], {}))
113
+
95
114
  return LifeList.from_json(response)
96
115
 
97
116
  @document_controller_params(get_observation_observers)