Skip to content
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

Feature request: use Annotated type hint for per-field documentation #376

Open
Talkless opened this issue Mar 16, 2023 · 6 comments
Open

Comments

@Talkless
Copy link
Contributor

Annotated[] type hint (https://docs.python.org/3/library/typing.html#typing.Annotated) could be used to add per-field textual description later seen in API Browser.

Description taken from Annotated[] (or other implementation) type hint could be printed in popup dialog where you enter call parameters, like this:

@jsonrpc_client.method('object.list')
@jwt_required()
def object__list(
        with_archived: Annotated[Optional[bool], 'Include archived objects if set to true. Default: true.'] = True
        without_virtual: Annotated[Optional[bool], 'Exclude virtual objects if set to true. Default: true.'] = True
) -> Annotated[List[int], 'List of objects ids. May be empty.']:
"""Get list of objects, including archived (objects that are no longer used) and virtual (objects that do not have a tracking device, or is a virtual asset)."""
    # ...

image

Also, whole function signature could be printed together with field descriptions, so instead of this currently shown in API Browser:

object.list(with_archived: Boolean, without_virtual: Boolean) -> Object

we would get:

object.list(
    with_archived: Boolean, 
        Include archived objects if set to true. Default: true.

    without_virtual: Boolean
        Exclude virtual objects if set to true. Default: true

) -> Object
    List of objects ids. May be empty.
@nycholas
Copy link
Member

It seems awesome! I need to explore more the features of Annotated decorator, 😄.

@Talkless
Copy link
Contributor Author

I am afraid that might look verbose? Alternative would be to parse docstring, finding @param foo boolean This And That, but that will require to duplicate param name, type, so might get out of sync fast...

Annotated looks like more in-line solution though. More magic :) .

nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 25, 2023
The expected behavior is that when using the modular way, the API
Browser merges in one, instead of having one API Browser for each.

Now, the server is aware of the `SERVER_NAME` Flask configuration,
it is being used by API Browser to request the correct server,
besides that, the API Browser is able to call servers in different
domains. For that configuration, the `JSONRPCSite` generates the
`path` and `base_url` variables from `SERVER_NAME`, `APPLICATION_ROOT`,
and `PREFERRED_URL_SCHEME`.

It is the first step to providing a Browse Schema to improve
documentation and examples from API (JSON-RPC methods).

Resolves: #388
See: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 27, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 27, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 27, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 27, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 29, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 29, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Mar 29, 2023
It's very important to provide a safe way to change
the Web Browsable API in the next tasks.

See also: #378, #377, #376, #374, #373, and #370
nycholas added a commit that referenced this issue Apr 5, 2023
It is the first change related to JSON-RPC Service Descriptor and
enhancement of Web Browsable API. The aim of these changes is to
create Metadata of the RPC method to be used by Web Browsable API.
The idea here is open the space to use `typing.Annotated`[1] in
parameters (and returns) and reflecting that in Web Browsable API,
such as:

```
def fn(
    name: typing.Annotated[str, MaxLenght(60), "First and last name of person"],
    age: typing.Annotated[int, ValueRange(1, 110), "Age of person"],
   ...
) -> typing.Annotated[str, "The data information of person"]:
```

The service descriptor was based on JSON Schema Service Descriptor
from JSON-RPC Spec[2], some changes were made to be more seems like
OpenRPC Spec[3], and others will do to adapt to the use case of Web
Browsable API.

**Note:** The RPC method `system.describe` was removed (replaced
by `rcp.describe`).

Resolves: #409
See also: #376

[1] - https://docs.python.org/3/library/typing.html#typing.Annotated
[2] - https://www.jsonrpc.org/historical/json-schema-service-descriptor.html
[3] - https://spec.open-rpc.org/
nycholas added a commit that referenced this issue Apr 5, 2023
It is the first change related to JSON-RPC Service Descriptor and
enhancement of Web Browsable API. The aim of these changes is to
create Metadata of the RPC method to be used by Web Browsable API.
The idea here is open the space to use `typing.Annotated`[1] in
parameters (and returns) and reflecting that in Web Browsable API,
such as:

```
def fn(
    name: typing.Annotated[str, MaxLenght(60), "First and last name of person"],
    age: typing.Annotated[int, ValueRange(1, 110), "Age of person"],
   ...
) -> typing.Annotated[str, "The data information of person"]:
```

The service descriptor was based on JSON Schema Service Descriptor
from JSON-RPC Spec[2], some changes were made to be more seems like
OpenRPC Spec[3], and others will do to adapt to the use case of Web
Browsable API.

**Note:** The RPC method `system.describe` was removed (replaced
by `rcp.describe`).

Resolves: #409
See also: #376

[1] - https://docs.python.org/3/library/typing.html#typing.Annotated
[2] - https://www.jsonrpc.org/historical/json-schema-service-descriptor.html
[3] - https://spec.open-rpc.org/
@nycholas
Copy link
Member

I thinking of using the library annotated-types to give more metadata to typing.Annotated, it's the same as Pydantic is using and it will bring compatibility to the project (Pydantic models, pure Python classes, Python data classes, and TypeVar), besides that, in the future supports the PEP-746.

import typing as t

import annotated_types as at

@jsonrpc_client.method('object.list')
@jwt_required()
def object__list(
        age: t.Annotated[int, Gt(18)],  # Valid: 19, 20, ...
                                                          # Invalid: 17, 18, "19", 19.0, ...
        factors: list[t.Annotated[int, at.Predicate(is_prime)]],  # Valid: 2, 3, 5, 7, 11, ...
                                                                                                # Invalid: 4, 8, -2, 5.0, "prime", ...
        my_list: t.Annotated[list[int], at.Len(0, 10)],  # Valid: [], [10, 20, 30, 40, 50]
                                                                                 # Invalid: (1, 2), ["abc"], [0] * 20
        with_archived: t.Annotated[
            t.Optional[bool], 
            t.Doc('Include archived objects if set to true. Default: true.')
        ] = True
        without_virtual: t.Annotated[
            t.Optional[bool], 
            t.Doc('Exclude virtual objects if set to true. Default: true.')
        ] = True
) -> t.Annotated[t.List[int], t.Doc('List of objects ids. May be empty.')]:
"""Get list of objects, including archived (objects that are no longer 
   used) and virtual (objects that do not have a tracking device, or 
   is a virtual asset).
"""
    # ...

On Browser API, this metadata will be converted to a well-defined schema object (every context-specific metadata will be converted to an attribute on the schema) to be treated, validated, and processed by Browser API. In other words, for the project to support new context-specific metadata, it will need a new implementation (unfortunately).

@Talkless
Copy link
Contributor Author

Talkless commented Oct 1, 2024

Looks good!

@Talkless
Copy link
Contributor Author

Talkless commented Oct 1, 2024

metadata will be converted to a well-defined schema object

Maybe this implies users might be able to use some "third-party" API browsers in the future, as that popular Swagger, etc?

@nycholas
Copy link
Member

nycholas commented Oct 1, 2024

@Talkless The project has support for OpenRPC, see here an example. It's closest to Swagger that we have for JSON RPC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants