Metadata-Version: 2.1
Name: objectref
Version: 2023.10.31
Summary: deal with object reference notations
Author: Aaron Tsang
License: MIT
Project-URL: Homepage, https://github.com/tsangwpx/objectref
Project-URL: Source, https://github.com/tsangwpx/objectref
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.7
Classifier: Typing :: Typed
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: mypy>=1.5.1; extra == "dev"
Requires-Dist: black>=23.9.0; extra == "dev"
Requires-Dist: isort>=5.12.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=7.4.2; extra == "test"
Requires-Dist: coverage[toml]>=6.4.4; extra == "test"

# objectref

objectref is a library to parse object references and find referenced objects.

## Notations

Object references are notations to locate modules or objects from the top level of a module.

```text
package.module
package.module:object.attr
```

The first form simply refers a module, while the second form an object in module.
It is also described in [here][1].

This library also supports relative object references (prefixed with dots).

[1]: https://packaging.python.org/en/latest/specifications/entry-points/#data-model

## Usage

* `find(ref: str, package: str | None = None) -> Any`

  The `find` function takes an object reference or `parse()` result,
  and returns the object pointed by the reference.

  The `package` argument must be set when the reference is relative.

  If qualified name is unspecified (the first form), the result is a module (`types.ModuleType` instance).

  `ModuleNotFoundError` is raised if the module cannot be found nor imported;
  `LookupError` if the object is not accessible from the top level of the module.

* `parse(ref: str) -> tuple[str, str | None]`

  The `parse` function takes an object reference,
  and results in a tuple of module name and qualified name.
  The qualified name would be `None` in first form.

  `ValueError` is raised if the reference is invalid.

## Example

```python
import os.path

import objectref

print(objectref.find("os.path") == os.path)  # True
print(objectref.find("os.path:join") == os.path.join)  # True
print(objectref.parse("os.path"))  # ("os.path", None)
print(objectref.parse("os.path:join"))  # ("os.path", "join")

```

## Entrypoint example

There are two main ways to execute a Python program.

* Run `python -m myapp.main` in shells, which executes the module as `__main__` under the hood.
  This relates to the first form of notation.

* Run `myapp` installed by pip. It is generated according to entrypoint defined in the project metadata
  (e.g. `myapp = myapp.main:main`).
  This requires the second form notation with callable objects.

Here is a code snippet to unify these two ways and start a program by its object reference.

```python
import objectref


def run(ref: str, package: str | None = None):
    modname, qualname = objectref.parse(ref)

    if qualname is None:
        # Use runpy to run a module with __name__ = "__main__"
        if modname.startswith("."):
            # runpy does not support relative module name.
            from importlib.util import resolve_name
            modname = resolve_name(modname, package)
        import runpy
        return runpy.run_module(modname, run_name="__main__")

    fn = objectref.find((modname, qualname), package)
    return fn()


if __name__ == "__main__":
    run("this")  # run the "this" module dynamically.

```
