Repository: encode/httpx

Issue:
Improve the API Reference
From the README: "The [Developer Interface](https://www.encode.io/httpx/api/) provides a comprehensive API reference.".

I'll be blunt: I'm not even sure its useful in its current form, let alone comprehensive. The API reference is always my pathway to learning a library. Much knowledge was just inherited/assumed from ``requests``. Most of the rest I've gone to the code for. To be fair, this isn't a major hassle for me; I like code diving. But all users aren't like me. Prose docs are good for some things and up to a point. But many times, what someone needs is best handled by a good API reference.

I love mkdocs. I much prefer it for most parts of documentation to Sphinx. And I know mkdocs2 may become a thing in the future, but that's too far out.

I know its been discussed at least a bit in the 1.0 blockers issue, but I think the API reference/autodoc story is a much bigger deal than that. So that's my reasoning for opening this issue.

So, my question is... are there any suggestions/constraints on a possible way forward on httpx's API reference/autodoc story in the near future? I haven't yet played with [pydoc-markdown](https://github.com/NiklasRosenstein/pydoc-markdown), but that may be one reasonable way.


Discussion:
I agree that section of the documentation is in need of some love, especially for descriptions of behaviors beyond the function signature.

I'm going to defer to @tomchristie or others for a solution here, I've only worked with Sphinx myself.
I used `pydocmd` in the past myself, in particular for the Bocadillo API reference. See [Responses](https://bocadilloproject.github.io/api/response.html#response) as an example. It's a very much OK Markdown-based API generator. It's got some quirks (for example, putting links in parameter descriptions is sometimes tricky, and I don't think it handles cross-referencing yet), but overall it can do a pretty good job.

In any case, I think switching to a documentation method that's as close as possible to the code (i.e.: in docstrings) can only be beneficial, as it makes it much easier to develop the API reference and keep it in sync with the actual behavior.

I think there's plenty of room for non-code contributions in HTTPX, and contributing docs is definitely one of them.
The low-touch approach to this *as an interim measure* would be to duplicate in two places.
It's not ideal but it could be sufficient just to get started.

An altenative would be for me to work on a markdown plugin to add support for this to the existing mkdocs, which seems like it could be a pretty big win.

Most important thing from my POV is that whatever we're doing needs to also be consistent across other encode projects - so eg. switching away from mkdocs just isn't feasible, but supplementing or adapting it can be.
I think the light touch approach that'll be worth looking into here will be seeing if I can adapt the docstring markdown plugin I was working on for mkdocs2 can instead simply be built for the existing mkdocs package. I may try to find some time in October to take this on.
Hi there,

Not that there's not anything actionable in this issue, but since we've got _some_ docstring-based docs infrastructure in place already I'll close this for housekeeping — would be great to see more of those though.

Resolving patch:
diff --git a/httpx/_client.py b/httpx/_client.py
--- a/httpx/_client.py
+++ b/httpx/_client.py
@@ -208,6 +208,14 @@ def build_request(
     ) -> Request:
         """
         Build and return a request instance.
+
+        * The `params`, `headers` and `cookies` arguments
+        are merged with any values set on the client.
+        * The `url` argument is merged with any `base_url` set on the client.
+
+        See also: [Request instances][0]
+
+        [0]: /advanced/#request-instances
         """
         url = self._merge_url(url)
         headers = self._merge_headers(headers)
@@ -603,6 +611,22 @@ def request(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Build and send a request.
+
+        Equivalent to:
+
+        ```python
+        request = client.build_request(...)
+        response = client.send(request, ...)
+        ```
+
+        See `Client.build_request()`, `Client.send()` and
+        [Merging of configuration][0] for how the various parameters
+        are merged with client-level configuration.
+
+        [0]: /advanced/#merging-of-configuration
+        """
         request = self.build_request(
             method=method,
             url=url,
@@ -626,6 +650,19 @@ def send(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a request.
+
+        The request is sent as-is, unmodified.
+
+        Typically you'll want to build one with `Client.build_request()`
+        so that any client-level configuration is merged into the request,
+        but passing an explicit `httpx.Request()` is supported as well.
+
+        See also: [Request instances][0]
+
+        [0]: /advanced/#request-instances
+        """
         timeout = self.timeout if isinstance(timeout, UnsetType) else Timeout(timeout)
 
         auth = self._build_auth(request, auth)
@@ -759,6 +796,11 @@ def get(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `GET` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "GET",
             url,
@@ -781,6 +823,11 @@ def options(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send an `OPTIONS` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "OPTIONS",
             url,
@@ -803,6 +850,11 @@ def head(
         allow_redirects: bool = False,  # NOTE: Differs to usual default.
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `HEAD` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "HEAD",
             url,
@@ -828,6 +880,11 @@ def post(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `POST` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "POST",
             url,
@@ -856,6 +913,11 @@ def put(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `PUT` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "PUT",
             url,
@@ -884,6 +946,11 @@ def patch(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `PATCH` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "PATCH",
             url,
@@ -909,6 +976,11 @@ def delete(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `DELETE` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return self.request(
             "DELETE",
             url,
@@ -921,6 +993,9 @@ def delete(
         )
 
     def close(self) -> None:
+        """
+        Close transport and proxies.
+        """
         self._transport.close()
         for proxy in self._proxies.values():
             if proxy is not None:
@@ -1137,6 +1212,22 @@ async def request(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Build and send a request.
+
+        Equivalent to:
+
+        ```python
+        request = client.build_request(...)
+        response = client.send(request, ...)
+        ```
+
+        See `AsyncClient.build_request()`, `AsyncClient.send()`
+        and [Merging of configuration][0] for how the various parameters
+        are merged with client-level configuration.
+
+        [0]: /advanced/#merging-of-configuration
+        """
         request = self.build_request(
             method=method,
             url=url,
@@ -1161,6 +1252,19 @@ async def send(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a request.
+
+        The request is sent as-is, unmodified.
+
+        Typically you'll want to build one with `AsyncClient.build_request()`
+        so that any client-level configuration is merged into the request,
+        but passing an explicit `httpx.Request()` is supported as well.
+
+        See also: [Request instances][0]
+
+        [0]: /advanced/#request-instances
+        """
         timeout = self.timeout if isinstance(timeout, UnsetType) else Timeout(timeout)
 
         auth = self._build_auth(request, auth)
@@ -1296,6 +1400,11 @@ async def get(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `GET` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "GET",
             url,
@@ -1318,6 +1427,11 @@ async def options(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send an `OPTIONS` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "OPTIONS",
             url,
@@ -1340,6 +1454,11 @@ async def head(
         allow_redirects: bool = False,  # NOTE: Differs to usual default.
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `HEAD` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "HEAD",
             url,
@@ -1365,6 +1484,11 @@ async def post(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `POST` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "POST",
             url,
@@ -1393,6 +1517,11 @@ async def put(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `PUT` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "PUT",
             url,
@@ -1421,6 +1550,11 @@ async def patch(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `PATCH` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "PATCH",
             url,
@@ -1446,6 +1580,11 @@ async def delete(
         allow_redirects: bool = True,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
     ) -> Response:
+        """
+        Send a `DELETE` request.
+
+        **Parameters**: See `httpx.request`.
+        """
         return await self.request(
             "DELETE",
             url,
@@ -1458,6 +1597,9 @@ async def delete(
         )
 
     async def aclose(self) -> None:
+        """
+        Close transport and proxies.
+        """
         await self._transport.aclose()
         for proxy in self._proxies.values():
             if proxy is not None:


Test patch: