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

Draft: Code generation for ThingClient subclasses #89

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rwb27
Copy link
Collaborator

@rwb27 rwb27 commented Dec 4, 2024

Currently, ThingClient subclasses are generated on-the-fly from a Thing Description. This "gets the job done" and means it's always possible to have a client that's up to date with the server. However, it would be nice to support static analysis, e.g. type checking, autocompletion, etc. with importable client classes for specific instruments.

Functionality

This PR implements client code generation, specifically:

  • A client class is created, subclassing ThingClient and named as per the title of the Thing Description.
  • Properties are added using their names from the TD
    • Property types are converted to Python type hints (with very basic conversion)
    • Read-only properties are made read-only. Write-only properties still have a read method.
    • Implementations use get_property, set_property as provided by ThingClient
  • Actions are added using names from the TD
    • The input schema is broken down into its properties (i.e. arguments), and each property's schema is converted to an argument with a type hint and default.
    • Required properties of the input schema become required arguments
    • Additional keyword arguments are always accepted and passed to the endpoint - TD DataSchema objects don't distinguish whether extra properties are allowed or not.
    • The output schema is converted to a return type hint
    • The function body calls invoke_action.
    • ... is used to distinguish between explicitly set None values, and unspecified values (i.e. not included in the JSON). This allows for values or defaults that are None to be handled differently from properties that are optional but don't have a default.

Schema conversion

Conversion from DataSchema to Python types is implemented partially. Types are converted recursively, with support for:

  • integer -> int
  • number -> float
  • boolean -> bool
  • string -> str
  • anyOf -> Union
  • array -> List (if an item type is specified, it's converted recursively)
  • object -> dict[str, Any
  • Anything else -> Any

The major limitation here is that object gets converted to a dict so we don't get hints for each property. We'd need to use a typeddict or dataclass or similar to get that functionality, and for now I'm going to consider it out of scope. However, there's one exception, which is the input schema of an Action. That is currently required to be an object, and we map each of its properties

Still to do

Before this is useful, we need to add a few things:

  • * More testing, including all the data types and actual use of the created class.
  • * Instantiation: currently, I believe calling from_url will overwrite the methods with generic ones.
  • * Implementation using HTTP or Direct clients - i.e. so one client class works on the server or a network client. This could be done by defining a protocol that provides get_property and friends and implementations of that roughly equivalent to DirectThingClient and ThingCilent, and then providing a base client class with class methods from_url and from_thing that create a subclass that mixes in either DirectThingClient or HTTPThingClient as appropriate.
  • * In both of the above cases, it would be nice to check that the methods/properties of the generated client class match the Thing Description of the thing we're connecting to. Extra affordances on the thing could be added dynamically, but it would be good to check the methods/properties of the client are all implemented on the server.

This generates Python code for a module containing a client.
The Python code generated for the test thing executes successfully, though
the name of the class is not quite right yet.
@VigneshVSV
Copy link

VigneshVSV commented Dec 4, 2024

I remember us talking about this. Now that I am somewhat finished with my obligations, I will have deeper look at the latest changes in labthings.

FYI, I already released pydantic based property types from this code base as part of my repository.

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.

2 participants