Skip to content

Add type hints to APIs #1297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 28, 2020
Merged

Add type hints to APIs #1297

merged 4 commits into from
Sep 28, 2020

Conversation

sethmlarson
Copy link
Contributor

@sethmlarson sethmlarson commented Jun 26, 2020

This will allow us to overcome the issue with query_params in that it cloaks the signature of each API function when it comes to query params. This means that IDEs will be able to show the full set of parameters for each API. Also nowadays Python libraries are moving towards type checking and the community will start desiring this functionality. It's nice when things work well out of the box. :)

Type Stubs / Python 2 compatible inline type hints

  • Using type stubs means we don't have to break Python 2 support as inline type hints are only possible in 3.6+
  • Python 2 compatible inline type hints are tough to parse since they aren't right next to the actual parameter, I've found them to be harder to read. They also would contribute to bloat of the existing .py files.

Interesting Places to Look

Anything that's not generated

All of these APIs were typed by hand, would appreciate reviews here the most.

Generated APIs

  • Most of the APIs (anything under client/ and _async/client/) aren't very interesting beyond the following:
    • Everything is typed with Any unless I explicitly know what the type should be.
    • Any API that uses HEAD as an HTTP method returns bool because it's guaranteed to by Transport.
    • All of the parameters that are available on every request (format, human, filter_path, request_timeout, etc)
      have an appropriate type hint applied to them unless they're also used by the API (in the case of format).
    • Why Optional[Any] instead of Any? They aren't the same.

Aiohttp Catch 22

Since we have aiohttp and yarl as optional dependencies we enter a catch-22 situation when it comes to mypy.

  • If we add # type: ignore to the line with import aiohttp then there is no problem when aiohttp isn't installed (because mypy can't find the type hints) but when it is installed mypy will then complain with "You don't need that # type: ignore comment" because aiohttp ships its own type hints and mypy isn't detecting any problems with them.
  • If we don't add # type: ignore to the import we have the reversed problem where there's no issue when aiohttp is imported but now mypy will complain that no type hints can be found when using --strict mode. (As users should!)

Solved this by adding _async/_extra_imports.py which basically imports aiohttp and yarl and adds a file-wide # type: ignore which mypy will never complain about. Feels like a work-around but tbh I'm unsure there's any way around this without requiring users to configure their mypy individually which I'd prefer not to require.

Requests gets around this unfortunate situation by being distributed within typeshed so mypy is always able to find type hints regardless of whether the package is installed or not. Verified this by deleting it's typeshed entry and the errors started occuring for requests as well.

Added AsyncConnection

Basically an abstract class that makes perform_request() and close async functions. If in the future
there's another async HTTP connection class or if users want to define their own.

Type hints for AsyncGenerators

An async generator can be written like so:

async def async_gen():
    yield 0  # This yield makes this a generator

but within a pyi file typically you don't include a function body which means we'll be missing the yield

async def async_gen() -> AsyncGenerator[None, int]: ...

but mypy then complains that calls to async for x in async_gen() have type Coroutine. Instead we should do this:

def async_gen() -> AsyncGenerator[None, int]: ... 

now the type signature matches exactly what the function is, a non-async function that returns an async generator.

External Project Type Checking Tests

Under test_elasticsearch/test_types I have two files, one async and one sync which import and exercise the APIs
to see what it feels like as an external user of the library when using mypy type checking. I run with [async] installed
against both async and sync and then uninstall aiohttp and run mypy against the sync with --strict enabled to ensure
we're not leaking type errors to our users.

TODO

  • Add typing to AIOHttpConnection.__init__

Closes #736

@sethmlarson sethmlarson force-pushed the type-hints branch 4 times, most recently from 84bc493 to 5fffd75 Compare July 2, 2020 16:04
@briancurtin
Copy link
Member

Would those aiohttp/yarl cases be handled by something like the following in a mypy.ini?

[aiohttp.*,yarl.*]
ignore_missing_imports = True

That way if hints are found they're used, but if they're not they're not.

@sethmlarson
Copy link
Contributor Author

@briancurtin That would work but every user would then have to make that configuration themselves which is a bummer.

@briancurtin
Copy link
Member

@briancurtin That would work but every user would then have to make that configuration themselves which is a bummer.

That seems a level deeper than I'm familiar with this going, though maybe I just misunderstand some of the layering here. If you're a consumer of elasticsearch, are typing issues inside the library actually exposed to consumers to where they'd need this? I've had to # type: ignore or the mypy.ini settings on things I directly use, but haven't seen problems with dependencies internal to those dependencies be exposed.

@sethmlarson
Copy link
Contributor Author

sethmlarson commented Jul 9, 2020

@briancurtin Yeah that's the case for mypy, especially with --strict/--follow-imports mode which many projects use. I created a few files that use the APIs (look under test_elasticsearch/test_types/...) and run mypy --strict on them to ensure we're not leaking type errors. Even without --strict/--follow-imports mode we still leak a type error when the aiohttp library isn't installed which is the default mode the library is installed without the work-around. :/

Users would either have to use the above config you provided or use --no-warn-unused-ignores/--ignore-missing-imports to mypy to completely silence type errors.

@sethmlarson
Copy link
Contributor Author

Data point: I've integrated this branch into a project using Eland which is partially type-checked and received no issues from elasticsearch-py when using mypy --strict.

@sethmlarson
Copy link
Contributor Author

I rebased this branch on master and collapsed everything into three commits. The first two are deeply reviewable since they're written by hand but the last one should be high-level reviewable since it's all generated code.

Copy link

@hub-cap hub-cap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this effort. Its great to see a project that is typed and python here. I don't have anything to add other than thank you and I will be taking these ideas back to my teams python projects as well. Keep up the great work!

@sethmlarson sethmlarson merged commit 208269a into master Sep 28, 2020
@sethmlarson sethmlarson deleted the type-hints branch September 28, 2020 16:12
bors bot added a commit to duckinator/parts.horse that referenced this pull request Sep 16, 2021
96: Pin elasticsearch to latest version 7.14.1 r=duckinator a=pyup-bot


This PR pins [elasticsearch[async]](https://pypi.org/project/elasticsearch) to the latest release **7.14.1**.



<details>
  <summary>Changelog</summary>
  
  
   ### 7.14.1
   ```
   Client

- Client is compatible with Elasticsearch 7.14.1
   ```
   
  
  
   ### 7.14.0
   ```
   Client

 Added

- Added check that client is connected to an Elasticsearch cluster. If the client isn&#39;t connected to a supported Elasticsearch cluster the `UnsupportedProductError` exception will be raised.

 APIs

 Search

- Added the `terms_enum` **beta** API

- Removed the `query_and_fetch` and `dfs_query_and_fetch` options in the `search_type` parameter to the `msearch`, `msearch_template` and `search_template` APIs

 Index Lifecycle Management

- Added the `ilm.migrate_to_data_tiers` API

 Machine Learning

- Added the `ml.reset_job` API

 Security

- Added the `security.saml_authenticate` API
- Added the `security.saml_complete_logout` API
- Added the `security.saml_invalidate` API
- Added the `security.saml_logout` API
- Added the `security.saml_prepare_authentication` API
- Added the `security.saml_service_provider_metadata` API

 SQL

- Added the `sql.delete_async` API
- Added the `sql.get_async` API
- Added the `sql.get_async_status` API

 Snapshots

- Added the `include_repository` parameter to `snapshot.get` API
- Added the `rarely_abort_writes` parameter to the `snapshot.repository_analyze` API
   ```
   
  
  
   ### 7.14.0a1
   ```
   Client

 Added

- Added check that client is connected to an Elasticsearch cluster. If the client isn&#39;t connected to Elasticsearch a `NotElasticsearchError` exception will be raised.

 APIs

 Search

- Added the `terms_enum` **beta** API

- Removed the `query_and_fetch` and `dfs_query_and_fetch` options in the `search_type` parameter to the `msearch`, `msearch_template` and `search_template` APIs

 Index Lifecycle Management

- Added the `ilm.migrate_to_data_tiers` API

 Machine Learning

- Added the `ml.reset_job` API

 Security

- Added the `security.saml_authenticate` API
- Added the `security.saml_complete_logout` API
- Added the `security.saml_invalidate` API
- Added the `security.saml_logout` API
- Added the `security.saml_prepare_authentication` API
- Added the `security.saml_service_provider_metadata` API

 Snapshots

- Added the `include_repository` parameter to `snapshot.get` API
- Added the `rarely_abort_writes` parameter to the `snapshot.repository_analyze` API
   ```
   
  
  
   ### 7.13.4
   ```
   Client

- Client is compatible with Elasticsearch 7.13.4
   ```
   
  
  
   ### 7.13.3
   ```
   Client

 Fixed

- `NameError` would be raised on Python 2.7 and 3.4 when a connection error would have otherwise been raised.
   ```
   
  
  
   ### 7.13.2
   ```
   ⚠️ **This release has been yanked on PyPI due to a regression in Python &lt;3.5, please use another version.**

 Client

 Fixed

- `Transport.perform_request()` now properly reraises `RecursionError` (Contributed by hmilkovi)
- `AIOHttpConnection` no longer sends `Accept-Encoding: gzip, deflate` when `http_compress=None`.
  Instead now sends no `Accept-Encoding` header.

 APIs

 Snapshot

- Added the `snapshot.repository_analyze` API
   ```
   
  
  
   ### 7.13.1
   ```
   Client

- Client is compatible with Elasticsearch 7.13.1
   ```
   
  
  
   ### 7.13.0
   ```
   Client

 Added

- Added support for compatibility header for Elasticsearch. If the environment variable `ELASTIC_CLIENT_APIVERSIONING=1` is set the client will send the headers Accept and Content-Type with the following value: `application/vnd.elasticsearch+json;compatible-with=7`.

 APIs

 Cat

- Added the `include_unloaded_segments` parameter to the `cat.nodes` API

 Snapshot Features

- Added the `features.reset_features` **experimental** API

 Fleet

- Added the `fleet.global_checkpoints` **expiremental** API

 Ingest

- Added the `ingest.geo_ip_stats` API

 Machine Learning

- Added the `ml.delete_trained_model_alias` API
- Added the `ml.preview_data_frame_analytics` API
- Added the `ml.put_trained_model_alias` API
- Changed the `ml.delete_data_frame_analytics`, `ml.delete_trained_model`, `ml.explain_data_frame_analytics`, `ml.get_data_fram_analytics`, `ml.get_data_frame_analytics_stats`, `ml.get_trained_models`, `ml.get_trained_models_stats`, `ml.put_trained_model`, `ml.start_data_frame_analytics`, `ml.stop_data_frame_analytics`, `ml.update_data_frame_analytics` APIs from **beta** to **stable**.

 Nodes

- Added `include_unloaded_segments` parameter to `node.stats` API

 Searchable Snapshots

- Added the `searchable_snapshots.cache_stats` **experimental** API

 Security

- Added the `security.clear_cached_service_tokens` **beta** API
- Added the `security.create_service_token` **beta** API
- Added the `security.delete_service_token` **beta** API
- Added the `security.get_service_accounts` **beta** API
- Added the `security.get_service_credentials` **beta** API

 Shutdown

- Added the `shutdown.delete_node` **experiemental** API
- Added the `shutdown.get_node` **experimental** API
- Added the `shutdown.put_node` **experimental** API

 Snapshots

- Added the `index_details` parameter to `snapshot.get` API

 Text Structure

- Changed the `text_structure.find_structure` API from **experimental** to **stable**
   ```
   
  
  
   ### 7.12.1
   ```
   APIs

 Text Structure

- Changed the `text_structure.find_text_structure` API from **experimental** to **stable**
   ```
   
  
  
   ### 7.12.0
   ```
   APIs

 Autoscaling

- Changed `autoscaling.delete_autoscaling_policy`, `autoscaling.get_autoscaling_policy`, and `autoscaling.put_autoscaling_policy` APIs from **experimental** to **stable**

 EQL

- Added `eql.get_status` API

 Logash

- Added `logstash.delete_pipeline`, `logstash.get_pipeline`, and `logstash.put_pipeline` APIs

 Machine Learning

- Removed the **experimental** `ml.find_text_structure` API

 Searchable Snapshots

- Added `storage` parameter to the `searchable_snapshots.mount` API
- Added `level` parameter to the `searchable_snapshots.stats` API

 Search

- Added the `min_compatible_shard_node` parameter to `search()`

 Text Structure

- Added **experimental** `text_structure.find_text_structure` API
   ```
   
  
  
   ### 7.11.0
   ```
   Client

 Added 

* Added the ``X-Elastic-Client-Meta`` HTTP header and the ``meta_header`` parameter for controlling the header (1473)
* Added ``ElasticsearchWarning`` which is raised when the ``Warning`` HTTP header is returned from Elasticsearch. `ElasticsearchDeprecationWarning` is now an alias for this warning type (1495)

 APIs

 Async Search

- Added the `async_search.status` API

 Autoscaling

- Added the `autoscaling.get_autoscaling_capacity` **experimental** API
- Removed the `autoscaling.get_autoscaling_decision` **experimental** API

 Cat

- Changed `cat.tasks` API parameters `node_id` and `parent_task` to `nodes` and `parent_task_id`

 Cluster

- Changed `cluster.delete_component_template`, `cluster.exists_component_template`, and `cluster.get_component_template`,
  `cluster.put_component_template` APIs from **experimental** to **stable**

 EQL

- Changed `eql.delete`, `eql.get`, `eql.search` APIs from **beta** to **stable**

 Indices

- Added `indices.migrate_to_data_stream` API
- Added `indices.promote_data_stream` API
- Added `expand_wildcards` parameter to the `indices.delete_data_stream` and `indices.get_data_stream` APIs
- Changed `indices.delete_index_template`, `indices.exists_index_template`, `indices.get_index_template`, `indices.put_index_template`, `indices.simulate_index_template`, and `indices.simulate_template` APIs moved from **experimental** to **stable**

 Machine Learning

- Added `ml.upgrade_job_snapshot` API
- Added `exclude_generated` parameter to `ml.get_data_frame_analytics`, `ml.get_datafeeds`, `ml.get_jobs`, and `ml.get_trained_models` APIs
- Changed `ml.delete_data_frame_analytics`, `ml.delete_trained_model`, `ml.explain_data_frame_analytics`, `ml.get_data_frame_analytics`, `ml.get_data_frame_analytics_stats`, `ml.get_trained_models`, `ml.get_trained_models_stats`, `ml.put_data_frame_analytics`, `ml.put_trained_model`, `ml.start_data_frame_analytics`, `ml.stop_data_frame_analytics`, `ml.update_data_frame_analytics` APIs from **experimental** to **beta**

 Rollup

- Added `rollup.rollup` API

 Transform

- Added `exclude_generated` parameter to `transform.get_transform` API

 Watcher

- Added `watcher.query_watches` API
   ```
   
  
  
   ### 7.11.0b1
   ```
   Client

 Added 

* Added the ``X-Elastic-Client-Meta`` HTTP header and the ``meta_header`` parameter for controlling the header (1473)
* Added ``ElasticsearchWarning`` which is raised when the ``Warning`` HTTP header is returned from Elasticsearch. `ElasticsearchDeprecationWarning` is now an alias for this warning type (1495)

 APIs

 Async Search

- Added the `async_search.status` API

 Autoscaling

- Added the `autoscaling.get_autoscaling_capacity` **experimental** API
- Removed the `autoscaling.get_autoscaling_decision` **experimental** API

 Cat

- Changed `cat.tasks` API parameters `node_id` and `parent_task` to `nodes` and `parent_task_id`

 Cluster

- Changed `cluster.delete_component_template`, `cluster.exists_component_template`, and `cluster.get_component_template`,
  `cluster.put_component_template` APIs from **experimental** to **stable**

 EQL

- Changed `eql.delete`, `eql.get`, `eql.search` APIs from **beta** to **stable**

 Indices

- Added `indices.migrate_to_data_stream` API
- Added `indices.promote_data_stream` API
- Added `expand_wildcards` parameter to the `indices.delete_data_stream` and `indices.get_data_stream` APIs
- Changed `indices.delete_index_template`, `indices.exists_index_template`, `indices.get_index_template`, `indices.put_index_template`, `indices.simulate_index_template`, and `indices.simulate_template` APIs moved from **experimental** to **stable**

 Machine Learning

- Added `ml.upgrade_job_snapshot` API
- Added `exclude_generated` parameter to `ml.get_data_frame_analytics`, `ml.get_datafeeds`, `ml.get_jobs`, and `ml.get_trained_models` APIs
- Changed `ml.delete_data_frame_analytics`, `ml.delete_trained_model`, `ml.explain_data_frame_analytics`, `ml.get_data_frame_analytics`, `ml.get_data_frame_analytics_stats`, `ml.get_trained_models`, `ml.get_trained_models_stats`, `ml.put_data_frame_analytics`, `ml.put_trained_model`, `ml.start_data_frame_analytics`, `ml.stop_data_frame_analytics`, `ml.update_data_frame_analytics` APIs from **experimental** to **beta**

 Rollup

- Added `rollup.rollup` API

 Transform

- Added `exclude_generated` parameter to `transform.get_transform` API

 Watcher

- Added `watcher.query_watches` API
   ```
   
  
  
   ### 7.10.1
   ```
   Client

 Fixed

* Fixed issue where the Scan helper would fail if a `scroll` response returned without a value for `_shards.skipped` ([1451](elastic/elasticsearch-py#1451))
* Fixed handling of IPv6 hosts with a port in the computed ``Connection.host`` property ([1460](elastic/elasticsearch-py#1460))
* Fixed documented task management API stability, should have been &quot;experimental&quot; ([1471](elastic/elasticsearch-py#1471))

 Changed

* Changed deprecated `collections.Mapping` in favor of `collections.abc.Mapping` for Python 3.9 ([1443](elastic/elasticsearch-py#1443))
   ```
   
  
  
   ### 7.10.0
   ```
   Client

 Added

* Added support for Elasticsearch 7.10 APIs
* Added basic type stubs for static type checking and IDE auto-complete of API parameters ([1297](elastic/elasticsearch-py#1297), [#1406](elastic/elasticsearch-py#1406))
* Added support for [Optimistic Concurrency Control options](https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html) (``_if_seq_no``/``_if_primary_term``) to bulk helpers ([#1387](elastic/elasticsearch-py#1387))
* Added support for passing ``_source`` with ``&quot;_op_type&quot;: &quot;update&quot;`` bulk helpers ([1387](elastic/elasticsearch-py#1387))
* Added API stability to doc-strings of API methods and documentation ([1410](elastic/elasticsearch-py#1410))

 Removed

* Removed explicit ``yarl`` dependency from ``[async]`` extra to avoid issue where pip would override ``aiohttp``&#39;s pin of ``yarl``. This was not a problem if you install with ``--use-feature=2020-resolver``. Users should see no changes. ([1401](elastic/elasticsearch-py#1401))

 Fixed

* Fixed bug where ``Connection.log_request_failure()`` call would receive the compressed HTTP body rather than uncompressed when an error is raised for ``RequestsHttpConnection`` ([1394](elastic/elasticsearch-py#1394))
* Fixed a typo in AsyncTransport where ``sniff_timeout`` was used instead of ``sniffer_timeout`` ([1431](elastic/elasticsearch-py#1431), contributed by HarrySky)

 Basic APIs

 Snapshot

* Added `snapshot.clone` method

 Index

* Added `require_alias` parameter to multiple index APIs

 X-Pack APIs

 Point in Time

* Added `close_point_in_time` and `open_point_in_time` methods

 Security

* Added `security.clear_api_key_cache` and `security.grant_api_key` methods

 Machine Learning

  * Deprecated `allow_no_jobs` parameter of `cat.ml_jobs` and `ml.close_job`, `ml.get_job_stats`, `ml.get_jobs`, `ml.get_overall_buckets` methods in favor of `allow_no_match` parameter
  * Deprecated `allow_no_datafeeds` parameter of `ml.get_datafeed_stats`, `ml.get_datafeeds`, `ml.stop_datafeed` in favor of `allow_no_match` parameter
  * Deprecated `include_model_definition` parameter of `ml.get_trained_models` method in favor of `include` parameter
   ```
   
  
  
   ### 7.10.0a2
   ```
   Added

* Added support for Elasticsearch 7.10 APIs
* Added basic type stubs for static type checking and IDE auto-complete of API parameters ([1297](elastic/elasticsearch-py#1297), [#1406](elastic/elasticsearch-py#1406))
* Added support for [Optimistic Concurrency Control options](https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html) (``_if_seq_no``/``_if_primary_term``) to bulk helpers ([#1387](elastic/elasticsearch-py#1387))
* Added support for passing ``_source`` with ``&quot;_op_type&quot;: &quot;update&quot;`` bulk helpers ([1387](elastic/elasticsearch-py#1387))

 Removed

* Removed explicit ``yarl`` dependency from ``[async]`` extra to avoid issue where pip would override ``aiohttp``&#39;s pin of ``yarl``. This was not a problem if you install with ``--use-feature=2020-resolver``. Users should see no changes. ([1401](elastic/elasticsearch-py#1401))

 Fixed

* Fixed bug where ``Connection.log_request_failure()`` call would receive the compressed HTTP body rather than uncompressed when an error is raised for ``RequestsHttpConnection`` ([1394](elastic/elasticsearch-py#1394))
   ```
   
  
  
   ### 7.10.0a1
   ```
   Added

* Added support for Elasticsearch 7.10 APIs
* Added basic type stubs for static type checking and IDE auto-complete of API parameters ([1297](elastic/elasticsearch-py#1297))
* Added support for [Optimistic Concurrency Control options](https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html) (``_if_seq_no``/``_if_primary_term``) to bulk helpers ([#1387](elastic/elasticsearch-py#1387))
* Added support for passing ``_source`` with ``&quot;_op_type&quot;: &quot;update&quot;`` bulk helpers ([1387](elastic/elasticsearch-py#1387))

 Removed

* Removed explicit ``yarl`` dependency from ``[async]`` extra to avoid issue where pip would override ``aiohttp``&#39;s pin of ``yarl``. This was not a problem if you install with ``--use-feature=2020-resolver``. Users should see no changes. ([1401](elastic/elasticsearch-py#1401))

 Fixed

* Fixed bug where ``Connection.log_request_failure()`` call would receive the compressed HTTP body rather than uncompressed when an error is raised for ``RequestsHttpConnection`` ([1394](elastic/elasticsearch-py#1394))
   ```
   
  
</details>


 

<details>
  <summary>Links</summary>
  
  - PyPI: https://pypi.org/project/elasticsearch
  - Changelog: https://pyup.io/changelogs/elasticsearch/
  - Repo: https://github.com/elastic/elasticsearch-py
</details>



Co-authored-by: pyup-bot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

query_param decorator does not preserve signature
3 participants