Currently, if you tell componentize-py to target a world that imports the interface foo:bar/baz@0.1.0, it will generate a Python module with one of the following names by default, depending on what other interfaces are included in that world:
wit_world.imports.baz if there are no other interfaces named baz referenced by the world
wit_world.imports.foo_bar_baz if there is at least one other interface named baz (e.g. x:y/baz@0.4.0) referenced by the world, but none in the same foo:bar package
wit_world_imports.foo_bar_baz_0_1_0 if there is at least one other interface named baz in the foo:bar package with a different version (e.g. foo:bar/baz@0.2.0)
In other words, the generated module will be given a short name if unambiguous, but a longer, more specific name otherwise. The advantage of this approach is that it's concise for simple cases. The main disadvantage is that, if the world is changed later to add new (versions of) interfaces, previously-unambiguous names may become ambiguous, requiring changes even for code which isn't using the new interfaces. That's especially painful for library packages, turning semver-compatible, purely additive WIT changes into semver-breaking library changes. It also creates a name clash hazard if code is generated for multiple worlds using multiple componentize-py bindings commands.
Considering similar tools for other languages:
- ComponentizeJS always generates full names for modules, thereby avoiding such issues.
- componentize-go generates mostly full names, always including the namespace and package name, but omits the version number if unambiguous unless the
--include-versions option is specified, so it still has the same hazard, although only with respect to versions, not package names.
- The Rust wit-bindgen nests the interface module in namespace and package modules, but omits version numbers if unambiguous, similar to
componentize-go.
Personally, I think ComponentizeJS makes the right choice here, and we should update the tools for other languages to match. In the case of componentize-py, that would mean making the --full-names option the default and either adding a --short-names option to turn it off, or not support turning it off (but still allow overriding the names on a case-by-case basis using the --import-interface-name and --export-interface-name options).
In addition, I'd like to propose that instead of placing the modules generated for each interface under wit_world.imports.* by default, make them top-level modules. That would more closely match the ComponentizeJS experience, where you use e.g. import "foo:bar/baz@0.1.0". In Python, we'd do import foo_bar_baz_0_1_0 (or import foo_bar_baz_0_1_0 as baz if desired). We'd still use wit_world for world-level types and functions, but stop using it for interface-level ones. Also, we'd still put code generated for exports under an exports (bikeshed-able) module to avoid clashes when the same interface is both imported and exported.
For example, consider the following WIT world:
package foo:bar@0.1.0;
interface baz {
wow: func();
}
world my-world {
import baz;
export baz;
import yay: func();
}
Today componentize-py bindings would generate the following modules by default:
wit_world.exports: contains a Baz abstract base class and baz submodule representing the exported interface
wit_world.imports.baz: contains the wow function from the imported interface
wit_world: contains the yay function imported at the world level
I'm proposing that changes to:
exports: contains a FooBarBaz010 abstract base class and foo_bar_baz_0_1_0 submodule representing the exported interface
foo_bar_baz_0_1_0: contains the wow function from the imported interface
wit_world: contains the yay function imported at the world level
Thoughts? Opinions? Bikeshed colors?
Currently, if you tell
componentize-pyto target a world that imports the interfacefoo:bar/baz@0.1.0, it will generate a Python module with one of the following names by default, depending on what other interfaces are included in that world:wit_world.imports.bazif there are no other interfaces namedbazreferenced by the worldwit_world.imports.foo_bar_bazif there is at least one other interface namedbaz(e.g.x:y/baz@0.4.0) referenced by the world, but none in the samefoo:barpackagewit_world_imports.foo_bar_baz_0_1_0if there is at least one other interface namedbazin thefoo:barpackage with a different version (e.g.foo:bar/baz@0.2.0)In other words, the generated module will be given a short name if unambiguous, but a longer, more specific name otherwise. The advantage of this approach is that it's concise for simple cases. The main disadvantage is that, if the world is changed later to add new (versions of) interfaces, previously-unambiguous names may become ambiguous, requiring changes even for code which isn't using the new interfaces. That's especially painful for library packages, turning semver-compatible, purely additive WIT changes into semver-breaking library changes. It also creates a name clash hazard if code is generated for multiple worlds using multiple
componentize-py bindingscommands.Considering similar tools for other languages:
--include-versionsoption is specified, so it still has the same hazard, although only with respect to versions, not package names.componentize-go.Personally, I think
ComponentizeJSmakes the right choice here, and we should update the tools for other languages to match. In the case ofcomponentize-py, that would mean making the--full-namesoption the default and either adding a--short-namesoption to turn it off, or not support turning it off (but still allow overriding the names on a case-by-case basis using the--import-interface-nameand--export-interface-nameoptions).In addition, I'd like to propose that instead of placing the modules generated for each interface under
wit_world.imports.*by default, make them top-level modules. That would more closely match theComponentizeJSexperience, where you use e.g.import "foo:bar/baz@0.1.0". In Python, we'd doimport foo_bar_baz_0_1_0(orimport foo_bar_baz_0_1_0 as bazif desired). We'd still usewit_worldfor world-level types and functions, but stop using it for interface-level ones. Also, we'd still put code generated for exports under anexports(bikeshed-able) module to avoid clashes when the same interface is both imported and exported.For example, consider the following WIT world:
Today
componentize-py bindingswould generate the following modules by default:wit_world.exports: contains aBazabstract base class andbazsubmodule representing the exported interfacewit_world.imports.baz: contains thewowfunction from the imported interfacewit_world: contains theyayfunction imported at the world levelI'm proposing that changes to:
exports: contains aFooBarBaz010abstract base class andfoo_bar_baz_0_1_0submodule representing the exported interfacefoo_bar_baz_0_1_0: contains thewowfunction from the imported interfacewit_world: contains theyayfunction imported at the world levelThoughts? Opinions? Bikeshed colors?