Devcontainer + vscode + uv + robotcode

I’d like to setup a project for a robotframework library robotframework-xyz providing library XyzLibrary which is consistent with uv (src/robotframework_xyz) as well as the naming convention of RF libs (XyzLibrary).

The project structure:

robotframework-xyz
  .devcontainer/
    devcontainer.json
  atests/
    xyz.robot
  src/
    robotframework_xyz/
      __init__.py

The __init__.py file contains the class XyzLibrary.

The content of devcontainer.json:

{
  "features": {
    "ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}
  },
  "postCreateCommand": "bash -i -c 'uv sync'",
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "ms-python.debugpy",
        "d-biehl.robotcode"
      ],
      // https://code.visualstudio.com/docs/python/settings-reference
      "settings": {
        // https://code.visualstudio.com/docs/python/settings-reference#_general-python-settings
        "python.terminal.activateEnvInCurrentTerminal": true,
        "python.defaultInterpreterPath": "/workspaces/robotframework-xyz/.venv/bin/python",
        "python.venvFolders": [
          "/workspaces/robotframework-xyz/.venv"
        ],
        "robotcode.robot.variables": {
          "ROOT": "/workspaces/robotframework-xyz"
        },
        "robotcode.robot.pythonPath": [
          ".",
          "./src/robotframework_xyz"
        ],

The content of pyproject.toml:

# https://docs.astral.sh/uv/reference/settings/
[tool.uv.sources]
robotframework-yxz = { workspace = true }

# https://robotcode.io/03_reference/config
[tool.robot]
python-path = [".", "./src/robotframework_xyz"]
paths = ["atests"]
output-dir = "results"

The content of __init__.py:

from typing import Protocol
from robot.api.deco import keyword

class SomeProtocol(Protocol):
    def some_method():
        ...

class XyzLibrary(SomeProtocol):
    def some_method(self, ...):
        ...
    @keyword("Keyword '${a}'")
    def some_keyword(self, a):
        ...

The content of xyz.robot:

*** Settings ***

Library             XyzLibrary

*** Test Cases ***
Some test
     Keyword    "Something"

In general RobotCode is working. But whatever I tried already results in an Importing test library 'XyzLibrary' failed: ModuleNotFoundError: No module named 'XyzLibrary' or a

Import definition contains errors.robotcode(ImportContainsErrors)
__init__.py(1, 1): Library 'robotframework_xyz.XyzLibrary' expected at least 1 non-named argument, got 0.

Has someone already setup a library with this setup and can help out?

did you installed it with pip (uv pip)?
If not you need to add the module dir to PYTHONPATH

No. I’ve installed uv into the devcontainer with

  "features": {
    "ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}
  },

and declared the pythonPath to RobotCode in the devcontainer

  "customizations": {
    "vscode": {"robotcode.robot.pythonPath": [
          ".",
          "./src/robotframework_xyz"
        ],

as well as in the pyproject.toml (equivalent to robot.toml)

[tool.robot]
python-path = [".", "./src/robotframework_xyz"]
paths = ["atests"]
output-dir = "results"

This should be the problem.
Usually __init__.py are used to declare the code as a module. They can even be empty.

You should (I think), move the code to a file with the name of your library (lowercase), then it can be imported in the __init__.py file.

I don’t know about configuration of vscode, but at least, I would not use relative paths to include in PYTHONPATH.

I assume you test your code first in the command line, and then configure the IDE.

I’ve recreated the venv. When I installed the lib in editable mode. I got

$ uv pip install -e .
Resolved 7 packages in 12ms
      Built robotframework-xyz @ file:///workspaces/robotframework-xyz
Prepared 1 package in 771ms
Uninstalled 1 package in 0.63ms
░░░░░░░░░░░░░░░░░░░░ [0/1] Installing wheels...                                                                                                                warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
         If the cache and target directories are on different filesystems, hardlinking may not be supported.
         If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 1 package in 1ms
 ~ robotframework-xyz==0.1.0 (from file:///workspaces/robotframework-xyz)

So I ran $ uv pip install -e --link-mode=copy . instead.

It might not be possible to be consistent with “default Python project layout and naming conventions” and get a library with the RF naming style.

From my understanding, what a Library statement does, is (in Python) import LibraryName from LibraryName followed by LibraryName(*args, **kwargs).

Now for the import, from a Python naming perspective, you’d actually want to import LibraryName from library_name since the top level folder of the package is the import name and folder names for modules / packages are snake_case according to PEP8.

There might be hacks to work around this, but I’d not be a fan, since then importing the library / module in RF would use a different name then using / testing the import in Python.

What I do for my RF libraries, is have the module name in CamelCase (e.g. OpenApiDriver) with in that folder an __init__.py and, e.g., openapi_driver.py (main entry point) containing the class OpenApiDriver. In the __init__.py there’s the import statement from OpenApiDriver.openapi_driver import OpenApiDriver and an __all__ = ["OpenApiDriver"]. That layout works for RF usage with names as expected and only violates PEP8 for the module name. The Python import for the module is import OpenApiDriver, same as the RF “import” name (i.e. the Library statement).

2 Likes

That’s what I tried to challenge :slight_smile:. I know of two project structure patterns right now:

  1. Traditional RF style is src/DatabaseLibrary/__init__.py > class DatabaseLibrary (and what you use in your multi RF lib package robotframework-openapitools)
  2. uv style is src/robotframework_construct/__init__.py > class roborframework_construct

1 is consistent with RF conventions. 2 is consistent with Python package naming conventions.

I agree. Actually it is better to stick with 1 and adapt the library package created with uv init --lib robotframework-xyz.

Some time ago I created a repo where I tried the same thing :wink:

The trick to setup a robot framework library with uv is the following:

Create your working folder and in that folder create a normal python library with uv, like this:

mkdir robotframework-dosomething
cd robotframework-dosomething
uv init --name DoSomething --lib   # notice here without robotframework-

then open the pyproject.toml and prepend robotframework- to the name setting, like this:

name = "robotframework-dosomething"

Now the “complicated” part, that depends on you build-system that your pyproject.toml uses, but if you use uv’s default hatchling you should add a new section to your pyproject toml file, like this:

[tool.hatch.build.targets.wheel]
packages = ["src/DoSomething"]   # attention this is case sensitive!!!

this tells hatchling where it can find the sources for the project when it builds a wheel package, thats what happens if you install the project in editable mode.

You can now add robotframework as a dependency with

uv add robotframework

and maybe add other dev dependencies like this:

uv add --dev robotframework-tidy
```


and now remove the old `.venv` and create a new one with 


```
uv sync
``` 


now you can select the python environment in vscode and everything should run and also robot. There is no need to setup what ever python paths or so, because the project is installed per default in editable mode.
1 Like

Worth pointing out that that the change Daniel describes above is very similar thing one has to do in “old way of packaging” python code if the library doesn’t reside in the root directory ..

Thx a lot! So far I used robot in projects only. Not in public libraries. I’ll merge into Official RF library project template(s)? - #8 by fkromer.

1 Like