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

Generate API clients based on description #373

Merged
merged 4 commits into from
Jul 6, 2023
Merged

Generate API clients based on description #373

merged 4 commits into from
Jul 6, 2023

Conversation

engelmi
Copy link
Member

@engelmi engelmi commented Jun 29, 2023

This PR

  • introduces a D-Bus API specification to client code generator.
  • adds generated python client code and reworks the existing python bindings
  • adds an additional lint step to check if there are API spec changes that are not yet in the generated code
  • adds a new CI job to publish a new pyhirte package to PyPI (manually triggered), whereas the version is linked to the hirte version defined in meson

@dougsland
Copy link
Contributor

dougsland commented Jun 29, 2023

[root@control python]# pip3 install pyhirte
Collecting pyhirte
  Downloading pyhirte-0.1.0-py3-none-any.whl (14 kB)
Collecting dasbus>=1.7
  Downloading dasbus-1.7-py3-none-any.whl (63 kB)
     |████████████████████████████████| 63 kB 1.3 MB/s
Installing collected packages: dasbus, pyhirte
Successfully installed dasbus-1.7 pyhirte-0.1.0
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv



# ./ListAllNodes
Traceback (most recent call last):
  File "/root/hirte/src/bindings/examples/python/./ListAllNodes", line 5, in <module>
    from pyhirte import HirteManager
  File "/usr/local/lib/python3.9/site-packages/pyhirte/__init__.py", line 1, in <module>
    from pyhirte.gen import *
  File "/usr/local/lib/python3.9/site-packages/pyhirte/gen/__init__.py", line 1, in <module>
    from .hirte import *
  File "/usr/local/lib/python3.9/site-packages/pyhirte/gen/hirte.py", line 7, in <module>
    class Hirte:
  File "/usr/local/lib/python3.9/site-packages/pyhirte/gen/hirte.py", line 18, in Hirte
    def get_proxy(self) -> InterfaceProxy | ObjectProxy:
TypeError: unsupported operand type(s) for |: 'ABCMeta' and 'ABCMeta'

It's hard to understand what's going on here without having someone that really knows what's going on in the parser/generator side, We will need to dup(mengel)

@dougsland dougsland marked this pull request as ready for review June 29, 2023 19:32
Copy link
Contributor

@dougsland dougsland left a comment

Choose a reason for hiding this comment

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

Let me start with: this is a nice work and really remember me a parsing/compiler classes. :)

Now few comments:

  • In general, good, should work (not there yet as I shared in the comment).
  • Such parser/generator would deserve a separate repo IMHO
  • Pro: Automatically generation of python/golang/whatever code.
  • Cons: Increase the complexity to maintain binding code as we need to understand the generator (instead of pure python as today). What happens if we want to generate golang? rust? A new shine language? Probably we will need to create a class/methods to adapt it?

Another point about simplicity, today we have all methods from Hirte() class which to my eyes seems pretty simple but with this implementation now we have more objects/classes (inherence) like HirteManager(), HirteNode(), HirteUnit() which is normal as the parser reads from the specification. How about we generate a docstring about the API too?

For the future, I would transform this project to generic parser/generator from dbus to whatever language users would like. Today is completely related to hirte but has potential to be generic and used for many of hundreds of projects out there.

Let's see the next round of patch to test it again. Also, would help if you could squash all these commits.

@engelmi
Copy link
Member Author

engelmi commented Jun 30, 2023

[root@control python]# pip3 install pyhirte
Collecting pyhirte
  Downloading pyhirte-0.1.0-py3-none-any.whl (14 kB)
Collecting dasbus>=1.7
  Downloading dasbus-1.7-py3-none-any.whl (63 kB)
     |████████████████████████████████| 63 kB 1.3 MB/s
Installing collected packages: dasbus, pyhirte
Successfully installed dasbus-1.7 pyhirte-0.1.0
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv



# ./ListAllNodes
Traceback (most recent call last):
  File "/root/hirte/src/bindings/examples/python/./ListAllNodes", line 5, in <module>
    from pyhirte import HirteManager
  File "/usr/local/lib/python3.9/site-packages/pyhirte/__init__.py", line 1, in <module>
    from pyhirte.gen import *
  File "/usr/local/lib/python3.9/site-packages/pyhirte/gen/__init__.py", line 1, in <module>
    from .hirte import *
  File "/usr/local/lib/python3.9/site-packages/pyhirte/gen/hirte.py", line 7, in <module>
    class Hirte:
  File "/usr/local/lib/python3.9/site-packages/pyhirte/gen/hirte.py", line 18, in Hirte
    def get_proxy(self) -> InterfaceProxy | ObjectProxy:
TypeError: unsupported operand type(s) for |: 'ABCMeta' and 'ABCMeta'

It's hard to understand what's going on here without having someone that really knows what's going on in the parser/generator side, We will need to dup(mengel)

The generator is not part of the pyhirte package, but the error is linked to the generated code. It seems that the | symbol - which can be used like the typing.Union - has been introduced in python3.10 (which I use) - see python3.10 release notes. So adding from __future__ import annotations should fix this for python3.9.
Good finding! @dougsland

@engelmi
Copy link
Member Author

engelmi commented Jun 30, 2023

* Cons: Increase the complexity to maintain binding code as we need to understand the generator (instead of pure python as today). What happens if we want to generate golang? rust? A new shine language? Probably we will need to create a class/methods to adapt it?

If we want to generate a client for another language like go, rust, etc. the only thing that is required are proper Jinja2 templates. For example, just passing the path to the rust template to the generator should result in the rust client for the hirte api.

Another point about simplicity, today we have all methods from Hirte() class which to my eyes seems pretty simple but with this implementation now we have more objects/classes (inherence) like HirteManager(), HirteNode(), HirteUnit() which is normal as the parser reads from the specification.

I refactored it a bit so we generate only one file, but still multiple classes - each class represents an interface. This is also a bit clearer in regards to which API "endpoint" is actually used and makes using them rather explicit, e.g. by using

node = HirteNode("laptop")

it makes sense to pass the node name to it. In addition, the lifetime of the dbus proxy is linked to the object - which is important for the monitor, for example - and the connection is reused per instance makes it more efficient than (re-)opening it per function call.

How about we generate a docstring about the API too?

That would be great to have and would be possible if we extend our API description e.g. by annotations or doc tags - these can be parsed by the generator and passed to the templates.

For the future, I would transform this project to generic parser/generator from dbus to whatever language users would like. Today is completely related to hirte but has potential to be generic and used for many of hundreds of projects out there.

Maybe, yes. But for now its rather hirte specific. I am still baffled that there is no such project yet, to be honest.

Also, would help if you could squash all these commits.

I'll rebased them a bit, so it should be easier to review. But since its a pretty big change, I'd not want to put everything in one commit.

@dougsland
Copy link
Contributor

Starting with some quick comments:

# ./ListAllNodes
Traceback (most recent call last):
  File "/root/hirte/src/bindings/examples/python/./ListAllNodes", line 9, in <module>
    print(f"Node: {node[0]}, State: {node[3]}")
IndexError: tuple index out of range

and

# shellcheck ./build-scripts/generate-bindings.sh

In ./build-scripts/generate-bindings.sh line 15:
    python3 $generator $data_dir $template_dir $output_file_path
            ^--------^ SC2086 (info): Double quote to prevent globbing and word splitting.
                       ^-------^ SC2086 (info): Double quote to prevent globbing and word splitting.
                                 ^-----------^ SC2086 (info): Double quote to prevent globbing and word splitting.
                                               ^---------------^ SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
    python3 "$generator" "$data_dir" "$template_dir" "$output_file_path"


In ./build-scripts/generate-bindings.sh line 16:
    black $output_file_path
          ^---------------^ SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
    black "$output_file_path"

For more information:
  https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...

@engelmi
Copy link
Member Author

engelmi commented Jul 3, 2023

./ListAllNodes
Traceback (most recent call last):
File "/root/hirte/src/bindings/examples/python/./ListAllNodes", line 9, in
print(f"Node: {node[0]}, State: {node[3]}")
IndexError: tuple index out of range

Fixed.

shellcheck ./build-scripts/generate-bindings.sh
...
Did you mean:
python3 "$generator" "$data_dir" "$template_dir" "$output_file_path"
...
Did you mean:
black "$output_file_path"

Added the quotes.

@alexlarsson
Copy link
Contributor

Why "temporarily removed client package from rpm"?

@engelmi
Copy link
Member Author

engelmi commented Jul 3, 2023

Why "temporarily removed client package from rpm"?

I couldn't figure out how to use the python macros to add it to the rpm, e.g. %pyproject_wheel. I would need help for this and wanted to tackle it in a follow-up PR. If its not difficult, we can do it in this PR, of course. @alexlarsson

@alexlarsson
Copy link
Contributor

I guess since it the python code was not in 0.3 we're not regressing if we temporarily remove it.

@dougsland
Copy link
Contributor

Why "temporarily removed client package from rpm"?

I couldn't figure out how to use the [python macros](https://docs.fedoraproject.org/en-US/packaging-
guidelines/Python/) to add it to the rpm, e.g. %pyproject_wheel. I would need help for this and wanted to
tackle it in a follow-up PR. If its not difficult, we can do it in this PR, of course. @alexlarsson

I had the same problem that why I used %install instead of these pre-defined macros. You see the same problem using the %install?

@dougsland
Copy link
Contributor

I guess since it the python code was not in 0.3 we're not regressing if we temporarily remove it.

Agreed, if required, spec can always wait a bit. It will not hurt.

engelmi added 4 commits July 6, 2023 11:37
- generated dbus api client for python
- refactored current python client
- extended the generated client
- refactored the python client examples
- temporarily removed client package from rpm

Signed-off-by: Michael Engel <[email protected]>
- added a lint CI step to check for api spec changes
- added a manually triggered job to publish pypi package

Signed-off-by: Michael Engel <[email protected]>
@dougsland
Copy link
Contributor

Great patch a few comments, let's merge and you can continue the journey with the minor changes to improve this solution.

BTW, could you please also verify this one? This is the only one example not working for me now.

./EnableUnit

Traceback (most recent call last):
File "/root/hirte/src/bindings/examples/python/./EnableUnit", line 10, in
if response.carries_install_info:
AttributeError: 'tuple' object has no attribute 'carries_install_info'

@dougsland
Copy link
Contributor

LGTM

@dougsland
Copy link
Contributor

sorry the delay to review, this week I took PTO days.

@dougsland dougsland merged commit 47a6038 into eclipse-bluechi:main Jul 6, 2023
ArtiomDivak pushed a commit to ArtiomDivak/BlueChi that referenced this pull request Jul 10, 2023
* aligned hirte api description with implementation

Signed-off-by: Michael Engel <[email protected]>

* implemented dbus api client generator

Signed-off-by: Michael Engel <[email protected]>

* rework of python client for hirte dbus api

- generated dbus api client for python
- refactored current python client
- extended the generated client
- refactored the python client examples
- temporarily removed client package from rpm

Signed-off-by: Michael Engel <[email protected]>

* extended github workflow for dbus api changes

- added a lint CI step to check for api spec changes
- added a manually triggered job to publish pypi package

Signed-off-by: Michael Engel <[email protected]>

---------

Signed-off-by: Michael Engel <[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.

3 participants