Skip to content

Escape string arguments as Python string literals - #16

Merged
ru551n merged 3 commits into
VUnit:mainfrom
ru551n:escape-string-args
Oct 15, 2026
Merged

ru551n merged 3 commits into
VUnit:mainfrom
ru551n:escape-string-args

Conversation

@ru551n

@ru551n ru551n commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

arg(value : string) and kwarg(kw, value : string) wrapped the text in double quotes verbatim, so a " or \ (or a line break) in the text produced invalid or wrong Python.

Change

Both now build the literal with p_quoted, which escapes ", \, LF and CR, and now also TAB and all other control characters (code < 32 and DEL) as \xNN. Bytes >= 128 are kept as they are so UTF-8 text is decoded by Python as the same characters. python_pkg.vhd is regenerated from the template. The user guide is updated, and the release note is the news fragment release_notes/news.d/16.breaking.md. News fragments are new: towncrier collects them into release_notes/unreleased.md when releasing, and the docs show them under Unreleased.

Behaviour change

Callers that escaped the text themselves (for example passing \n to get a newline) now get that text escaped again, and must pass the plain text instead.

Tests

  • tb_python_pkg_bridge: round trip of quote, backslash, single quote, LF, TAB, CR, NUL, DEL and non-ASCII through arg/kwarg, plus the to_call_str output.
  • tb_python_pkg (also run on VHPI simulators): length/ord checks of escaped strings.
  • Full tests/run.py, the embedded_python example and pytest tests/test_python_bridge.py pass on NVC and GHDL.

🤖 Generated with Claude Code

Comment thread release_notes/unreleased.md Outdated
## Changes

- `import_module_from_file` resolves a relative path against the directory of the testbench file, like `exec_file`, rather than against the working directory of the simulator. It must be called after `test_runner_setup`. Callers that relied on a path relative to the working directory must pass an absolute path instead. See #7.
- `arg(value : string)` and `kwarg(kw, value : string)` now produce a valid Python string literal: quotes, backslashes and control characters in the text are escaped. Callers that escaped the text themselves (for example passing `\\n` to get a newline) now get that text escaped again, and must pass the plain text instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Make it a news fragment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Claude (AI assistant) here, on behalf of @ru551n:

Fixed in a482910. The note is now release_notes/news.d/16.breaking.md. The repository had no news fragments yet, so the commit also:

  • configures towncrier ([tool.towncrier] in pyproject.toml, types breaking/feature/bugfix/doc/misc), so towncrier build --version <version> collects the fragments into release_notes/unreleased.md when releasing;
  • shows the fragments under Unreleased in the docs;
  • makes tools/release.py validate --tag fail while fragments are left.

ru551n and others added 2 commits October 15, 2026 09:34
arg and kwarg for string now escape quotes, backslashes and control
characters, so any text round-trips exactly.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The release notes of the next release are now news fragments in
release_notes/news.d, which towncrier collects into
release_notes/unreleased.md when releasing. The documentation shows
them under Unreleased, and the release check fails while any are left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ru551n
ru551n force-pushed the escape-string-args branch from fa73b4c to a482910 Compare October 15, 2026 07:35
The draft of the news fragments is rendered by towncrier itself, through
the towncrier-draft-entries directive, instead of by code in conf.py. The
release notes page is generated as Markdown, since the directive parses
the Markdown towncrier writes like the page holding it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ru551n
ru551n merged commit 994ddf6 into VUnit:main Oct 15, 2026
29 checks passed
ru551n added a commit to ru551n/vunit-python-bridge that referenced this pull request Oct 15, 2026
- counter_vc and filter_vc read p_model of their handle, like the user
  guide and VUnit's own verification components, instead of a get_model
  accessor.
- The Python objects page no longer repeats that string arguments are
  passed verbatim, a semantic of python_pkg that VUnit#16 changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ru551n added a commit to ru551n/vunit-python-bridge that referenced this pull request Oct 16, 2026
- counter_vc and filter_vc read p_model of their handle, like the user
  guide and VUnit's own verification components, instead of a get_model
  accessor.
- The Python objects page no longer repeats that string arguments are
  passed verbatim, a semantic of python_pkg that VUnit#16 changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ru551n ru551n mentioned this pull request Oct 20, 2026
ru551n added a commit that referenced this pull request Oct 21, 2026
* Support Riviera-PRO through the bridge with VHPI

Riviera-PRO has no direct binding (VHPIDIRECT), and was
served by a separate VHPI application with a subset of the API. It now
calls the bridge like the other simulators, through a VHPI front end,
native/vhpi.c, that registers a foreign subprogram per entry point of
bridge.h. The front end is compiled into a variant of the library, like the
FLI one for Questa, against the vhpi_user.h of the simulator, and the
generated attributes name the library by absolute path without its suffix.
Strings cross as vectors of character positions, since Riviera-PRO ends a
string value at a line feed.

The VUnit interface of Riviera-PRO has no run environment hook,
so the setup sets VUNIT_PYTHON_BRIDGE_CONFIG in the environment of the VUnit
process, which vsim inherits.

The VHPI application (python_pkg_vhpi.vhd, native/vhpi/,
foreign_application.py) and the expected failures of the tests on VHPI are
removed. Two test names lose their brackets, which VUnit's Tcl scripts for
Riviera-PRO evaluate as commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: Riviera-PRO only, explicit front ends

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Make the release note a news fragment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Prototype Python objects owned by VHDL

python_object_t is an instance of a Python class living in a session of its
own, created on first use and called with call(object, "method", args).
Every VC instance can get an object of its own without namespace or path
handling, and a bench can create one and pass it to a VC through a generic.

Hand-written prototype for review of the API; the full set of result types
would be generated once it is agreed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Generate the call and eval overloads of python_object_t

python_object_pkg.vhd is generated from python_object_pkg.vhd.in by
generate_python_pkg.py, from the same result tables as python_pkg, so an
object has every result type of call and eval, the procedure results and
the same aliases. A test checks that both generated packages are up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say that python_object_pkg.vhd is generated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rename object_or_new to get

Like get_logger and get_id, it gives the object it is given or a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document Python objects and report objects that cannot be created

An object whose class cannot be imported or constructed reports that error
once and is then bound to a stand-in raising the reason on every use,
rather than leaving self undefined. The user guide gets a Python objects
section, and the README and the release notes mention them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the constructor new_python_object and enumerate anonymous objects

new_object is now new_python_object and get is get_python_object, as
VUnit names the constructors and getters of its types. An object without
an id is python_bridge:python:object:<n>, enumerated like VUnit's
verification components. The user guide makes the handle constructor of a
VUnit style verification component the place to create its object, which
tests/counter_vc_pkg.vhd now shows; the handle-less component taking its
instance path is tests/path_counter_vc.vhd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add exec and get_logger for objects and report objects on VHPI

exec(object, code) executes code with the object as self, and
get_logger(object) is the logger reporting its errors. On
Riviera-PRO/Active-HDL, which have no sessions, the first use of an object
says that objects require NVC, GHDL or Questa. The user guide notes that an
integer_array_t cannot be a constructor argument of an object made during
elaboration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show Python objects as backends in the embedded_python example

filter_vc is a verification component whose handle, made by
new_filter_vc, holds the Python object that is its behaviour. The new
test case drives three instances: two with objects of their own and one
given an object of the testbench, which the testbench queries too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show how a model class is made importable, fix the class example

From the verification component pattern of #14: the model of a verification
component as a package next to it, put on PYTHONPATH by the run script or
installed with pip install -e. The class example of the independent models
section gets its file from the model_file generic of the component instead
of runner_cfg, which a component does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Polish python_object_pkg

Name the failures of objects after what failed: new_python_object("<class>")
for a failed creation and call("<method>") for the procedure form of a
method call, rather than an exec of their session. p_create and p_self are
private to the package body, and the PROTOTYPE marker is gone. The user
guide shows a component calling its object in a process and how errors are
named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cover create and get_session of objects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Lay out the instantiations like standard ones

One association per line in the generic and port maps of the tests, the
example and the user guide, the layout speja gives them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address the review of Python objects

- A class is named by a plain dotted name, resolved from the longest prefix
  that is a module, so nested classes work without the module:Class form.
- __vunit__.create is __vunit__.instantiate_as_self, after what it does.
- p_supports_sessions is gone: the FFI of Riviera-PRO/Active-HDL already
  reports the first use of a session other than the default one.
- filter_vc of the example takes a clock and a valid input.
- The user guide gets a "Where Python code is found" chapter on the run
  script directory, the Python environment, PYTHONPATH, file names and
  global module names, and clearer paragraphs on backends of verification
  components and shared objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say plainly when Python code is not found without setup

The Where Python code is found chapter gets a subsection per topic, and
the verification component example states that its folder is not
importable until the run script adds it to PYTHONPATH, before showing how.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Simplify python_object_pkg and the runtime

- pydoc.locate resolves dotted class names instead of a hand-written loop.
- The identity check is in VHDL only, which also catches a duplicate at
  elaboration; the Python check of the session was a second copy of it.
- Runtime.namespace is the property itself rather than a wrapper of it.
- create is the procedure the generated code calls, without p_create
  behind it, p_self is inlined and the operation of a failed creation is
  stored rather than rebuilt from the class name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Find classes of objects more clearly and report a failed object on every use

A class name now starts in the default session, so a class reached
through a name defined there, like "np.ndarray", is found, and is
otherwise imported with importlib rather than pydoc, which is no longer
imported at every simulation start. A name that is not found, or not a
class, is reported in one line rather than with the traceback of the
bridge. The stand-in of an object that could not be created also fails
on assignments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fail early on objects that cannot work and name the call in its errors

- new_python_object reports, on the logger of the identity, an identity
  already used by an object or by a session made with new_session, and
  a simulator without sessions (Riviera-PRO/Active-HDL), and then
  returns null_python_object rather than carrying on. Using
  null_python_object is reported on python_logger.
- The VHPI p_exec stub reports and executes nothing, rather than
  running the code in the default session.
- Every form of call on an object reports its errors as
  call("<method>"), like the procedure did: the call overloads evaluate
  with p_eval and the result getters, which the FFI packages now all
  have.
- create is named create_now, which is what it does.
- Tests of the error paths: a used identity, an object that cannot be
  created, the naming of call errors, separate sessions and numbering,
  exec and get_logger before creation, null_python_object, the eval
  procedures and the identities of create_std_cfg.
- The generated lines are at most 120 characters, and the docstring of
  the generator describes both packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Merge where Python code is found into File paths and update the objects

The section on how modules are found is now the "Python modules" part
of "File paths", whose table already covered file names, with one
recipe for PYTHONPATH. The objects chapter describes the new identity
rule, the failure on Riviera-PRO/Active-HDL, the class lookup and the
error names of call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Apply the learnings of trying objects in a real project

Converting a stream-width model kept in module globals to an object per
source, in a project that used exec_file models before, showed:
- A misspelled class in a module that does import said only "No class
  named", which reads like a missing module. It now names the module
  and the missing attribute.
- The user guide gets a recipe for moving from a model with module
  state to an object, says that the id is optional, and recommends
  create_now to report wrong constructor arguments at the start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pair new_python_object with create_python_object

new_python_object makes an object that Python constructs on first use;
create_python_object makes one that is constructed by the call, so a
wrong class or constructor argument is reported there. They replace
new_python_object with create_now, and the step that constructs an
object on first use is private.

create_python_object only works in a process: NVC cannot find the
foreign functions of the bridge during elaboration ("foreign function
vpy_buffer_clear not found") and GHDL cannot call them then. The user
guide has a table of when to use each, and errors name the one called.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give Python objects a page of their own in the documentation

The Python objects chapter moves from Running Python code to its own page, after
Sessions, and python_object_pkg gets a page in the VHDL API reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: drop the simulator check of objects, new docs example

Remove p_sessions_supported: on Riviera-PRO/Active-HDL, the first use of an
object already fails in the VHPI primitives, and whether Riviera-PRO is
supported is a question of its own. The shared object example of the docs
is a memory model instead of a scoreboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Get Python objects by identity or full name

get_python_object(id) and get_python_object(name) return the object made
with that identity, like get_logger, but report an identity without an
object as a failure instead of making one. new_python_object and
create_python_object take the identity by its full name as well. The
get_python_object giving a component a default backend is replaced by an
if in the constructor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bind a Python object to obj instead of self in its session

self is also the first parameter of the methods of classes defined in
the session, which made code mixing the two hard to read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name a Python object made without an id after its class

Objects without an id are enumerated per class, the dotted class name
giving the identity hierarchy under python_bridge:python:object, e.g.
python_bridge:python:object:models:counter_model:Counter:1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Return null_python_object for an identity without an object

get_python_object no longer reports a failure for an identity without
an object, so a testbench can check whether the object is made and make
it otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give verification components a constructor per use case

A component making its own object and a component given one have
constructors of their own instead of an optional model parameter. The
user guide describes the identities of components and their objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give path_counter_vc a handle instead of several generics

The handle holds the step and the id. Without an id the component still
takes its instance path as the identity, in its process, and the
testbench reaches the object by that path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the named eval and call forms, adding the alias call_integer

The tests and the user guide use eval_integer, call_real and the like
rather than qualified expressions. The call returning integer,
call_integer_w_arg, gets the alias call_integer in python_pkg and
python_object_pkg, named like the other results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add function forms of the vector results sized like to_unsigned

eval and call of std_ulogic_vector, signed and unsigned results, in
python_pkg and for objects, are also functions taking the width as a
length or as size_res. They call the procedure forms, so an error
returns a vector of 'U'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Leave the result of a failed sized function out of the docs

A failure stops the test before the function returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Align the docs and comments of Python objects with the latest changes

- The python_pkg comment and the example no longer say that signed and
  unsigned results are procedures only; the example shows the sized
  function too.
- The aliasing of call is described for the functions without a width.
- The new features of the release notes are listed under New features.
- One class name in the identity examples of the user guide, and a class
  name given to a constructor rather than as a generic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the model of a handle directly and drop a python_pkg caveat

- counter_vc and filter_vc read p_model of their handle, like the user
  guide and VUnit's own verification components, instead of a get_model
  accessor.
- The Python objects page no longer repeats that string arguments are
  passed verbatim, a semantic of python_pkg that #16 changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Make the release notes news fragments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the news fragment of Python objects, drop the call_integer one

The details are in the user guide, and the call_integer alias is nothing
a user needs to know about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name objects without an id python_object:<class>:<n>

The default identity was python_bridge:python:object:package:module:Class:<n>.
python_object:package.module.Class:<n> is shorter and as unique: the
counter separates the objects of a class and the dotted class name the
classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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