GithubHelp home page GithubHelp logo

mityax / rustimport Goto Github PK

View Code? Open in Web Editor NEW
216.0 3.0 12.0 88 KB

Import Rust source files directly from Python!

License: MIT License

Python 100.00%
pyo3 python python-rust python3 rust rust-bindings rust-python

rustimport's Introduction

rustimport - Import Rust directly from Python!

rustimport was heavily inspired by and is partly based upon cppimport. Check it out if you're interested in the same functionality for C and C++!

Installation

Install with pip install rustimport.

A quick example

Save the Rust code below as somecode.rs.

// rustimport:pyo3

use pyo3::prelude::*;

#[pyfunction]
fn square(x: i32) -> i32 {
    x * x
}

Then open a Python interpreter and import the Rust extension:

>>> import rustimport.import_hook
>>> import somecode  # This will pause for a moment to compile the module
>>> somecode.square(9)
81

Hurray, you've called some Rust code from Python using a combination of rustimport and pyo3

This workflow enables you to edit both Rust files and Python and recompilation happens automatically and transparently! It's also handy for quickly whipping together an optimized version of a slow Python function.

To easily create a new single-file extension (like above), or a complete crate, use the provided tool:

$ python3 -m rustimport new my_single_file_extension.rs
# or create a full rust crate:
$ python3 -m rustimport new my_crate

And import it from Python:

>>> import rustimport.import_hook
>>> import my_single_file_extension, my_crate
>>> my_single_file_extension.say_hello()
Hello from my_single_file_extension, implemented in Rust!
>>> my_crate.say_hello()
Hello from my_crate, implemented in Rust!

Smooth!

An explanation

Okay, now that I've hopefully convinced you on how exciting this is, let's get into the details of how to do this yourself. First, the comment at top is essential to opt in to rustimport. Don't forget this! (See below for an explanation of why this is necessary.)

// rustimport:pyo3

The bulk of the file is a generic, simple pyo3 extension. We use the pyo3 crate, then define a simple function that squares x, and rustimport takes care of exporting that function as part of a Python extension called somecode.

Templating & Pre-Processing

rustimport offers several layers of customization. This is archieved through a simple pre-processor and templates (well, the only existing template at the moment is pyo3 - pull requests welcome :D).

What rustimport did for you in the background

The first example in this Readme is the simplest possible form of using rustimport. You just tell rustimport to use the pyo3 template by writing rustimport:pyo3 in the first line, and define a function annotated with pyo3's #[pyfunction] macro. In the background, rustimport handled a lot of stuff for you:

  1. It set up a minimalistic folder structure for a rust crate with your source code in a temporary location.
  2. It generated a Cargo.toml file with the basic configuration for pyo3 and your extension:
[package]
name = "somecode"
version = "0.1.0"
edition = "2021"

[lib]
name = "somecode"
crate-type = [ "cdylib",]

[dependencies.pyo3]
version = "0.16.2"
features = [ "extension-module",]
  1. It generated a code block exporting your method and appended it to the end of your file:
#[pymodule]
fn somecode(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
  m.add_function(wrap_pyfunction!(square, m)?)?;
  Ok(())
}

Customizing an extension

You can do all the above yourself. rustimport will detect that and only fill in the missing parts to make your extension work.

1. Extending Cargo.toml

For example, to add additional contents to the generated Cargo.toml file, use the special //: comment syntax at the top of your .rs file:

// rustimport:pyo3

// Set the library's version and add a dependency:
//: [package]
//: version = "1.2.3"
//:
//: [dependencies]
//: rand = "0.8.5"

use rand::Rng;

#[pyfunction]
fn myfunc() {
    println!("{}", rand::thread_rng().gen_range(0..10))
}

2.Tracking additional source files

To track additional files for changes, use the special //d: comment syntax:

//d: ../other-folder/somefile.rs
//d: ../*.rs
//d: ./my-directory/**/*.json

// --snip--

rustimport will now track files matching these patterns too and re-compiles your extension if any of them changes.

3. Full customization for more control

If you write a more complex extension, it's preferrable to just create a normal Rust crate:

$ python3 -m rustimport new my_crate
$ tree my_crate
my_crate
├── Cargo.toml
├── .rustimport
└── src
    └── lib.rs

The crate contains all necessary configuration to be directly be imported by rustimport and also some additional explanations on how to configure manually.

Using environment variables

rustimport exposed most of it's settings via environment variables. This can be pretty handy in development but also in production environments.

For example, to force recompilation, just run your script like this:

RUSTIMPORT_FORCE_REBUILD=true python my_script.py

Or to instruct the compiler to optimize binaries (for example to examine the real performance boost rust gives you) run it like this:

RUSTIMPORT_RELEASE_BINARIES=true python my_script.py

Take a look at settings.py for all available environment variables.

Using in Jupyter

rustimport supports compiling code in a Jupyter notebook. To enable this feature load the rustimport extension from within the Jupyter notebook:

%load_ext rustimport

Then, prefix a cell with the %%rustimport marker to compile it:

%%rustimport
use pyo3::prelude::*;

#[pyfunction]
fn square(x: i32) -> i32 {
    x * x
}

%%rustimport supports release mode and force compilation with flags:

%%rustimport --release --force
...

Usage in production

1. Building release binaries

In production deployments you usually don't want to include the Rust toolchain, all the sources and compile at runtime. Therefore, a simple cli utility for pre-compiling all source files is provided. This utility may, for example, be used in CI/CD pipelines.

Usage is as simple as

python -m rustimport build --release

This will build a release-optimized binary for all *.rs files and Rust crates in the current directory (and it's subdirectories) if they are eligible to be imported (i.e. contain the // rustimport comment in the first line or a .rustimport file in case of a crate).

Alternatively, you may specifiy one or more root directories or source files to be built:

python -m rustimport build --release ./my/root/folder/ ./my/single/file.rs ./my/crate/

Note: When specifying a path to a file, the header check (// rustimport) is skipped for that file.

2. Toggling release mode on

To further improve startup performance for production builds, you can opt-in to skip the checksum and compiled binary existence checks during importing by either setting the environment variable RUSTIMPORT_RELEASE_MODE to true or setting the configuration from within Python:

rustimport.settings.release_mode = True

This essentially just disables the import hook and uses the standard python utilities to import the pre-compiled binary.

Warning: Make sure to have all binaries pre-compiled with when in release mode, as importing any missing ones will cause exceptions.

In case you would, for whatever reason, like the binaries to be checked and built in production too, set rustimport.settings.compile_release_binaries to True to use release-optimized binaries.

Frequently asked questions

What's actually going on?

Sometimes Python just isn't fast enough. Or you have existing code in a Rust crate. So, you write a Python extension module, a library of compiled code. I recommend pyo3 for Rust to Python bindings.

I discovered that my productivity is slower when my development process goes from Edit -> Test in just Python to Edit -> Compile -> Test in Python plus Rust. So, rustimport combines the process of compiling and importing an extension in Python so that you can just run import foobar and not have to worry about multiple steps. Internally, rustimport looks for a file foobar.rs or a Rust crate (discovered through foobar/Cargo.toml). Assuming one is found, the comments at it's beginning are parsed for either a template (rustimport:pyo3) or a cargo manifest, then it's compiled and loaded as an extension module.

Does rustimport recompile every time a module is imported?

No! Compilation should only happen the first time the module is imported. The Rust source is compared with a checksum on each import to determine if any relevant file has changed. Additional dependencies can be tracked by adding to the header comments:

//d: ../myothercrate/**/*.rs
//d: ../myothercrate/Cargo.toml

By default, rustimport tracks all *.rs files as well as Cargo.toml and Cargo.lock for crates and no additional dependencies for single-file Rust extensions.

rustimport isn't doing what I want, can I get more verbose output?

rustimport uses the standard Python logging tools. Thus, you can enable logging like this:

import logging
logging.basicConfig(level=logging.DEBUG)  # or logging.INFO for a bit less verbosity
# ... do some rustimport stuff here

It's fast, but can it get even faster?

To create release-optimized binaries, set

rustimport.settings.compile_release_binaries = True

Or set the environment variable RUSTIMPORT_RELEASE_BINARIES to true

Compilation might be a little bit slower now due to rust's optimization mechanisms, but at runtime the extension is significantly faster in most cases.

How can I force a rebuild even when the checksum matches?

Set:

rustimport.settings.force_rebuild = True

Or set the environment variable RUSTIMPORT_FORCE_REBUILD to true

And if this is a common occurrence, I would love to hear your use case and why the normal dependency tracking is insufficient!

Can I use something else than pyo3?

Sure! Though I recommend using pyo3 due to it's simplicity, you're completely free to use any other library, for example rust-cpython.

There is an example using rust-cpython in examples/doublecount.rs

How can I make compilation faster?

Compilation happens incrementally by default. That is, the first compilation might take a bit, but subsequent ones are usually much faster.

rustimport uses a temporary directory for caching, which is deleted after a reboot on most systems. Therefore it might be beneficial for you to set a custom cache directory to have a more permanent cache:

rustimport.settings.cache_dir = os.path.realpath("./.rust-cache")

Or - you guessed it - use the RUSTIMPORT_CACHE_DIR environment variable.

If this directory doesn't exist, it will be created automatically by rustimport.

Apple, huh?

Yes, macOS is supported. No additional config should be necessary for pyo3 as the required linker args are set automatically by rustimport.

Why does the import hook need "rustimport" on the first line of the .rs file?

Modifying the Python import system is a global modification and thus affects all imports from any other package. As a result, when cppimport was first implemented, other packages (e.g. scipy) suddenly started breaking because import statements internal to those packages were importing C or C++ files instead of the modules they were intended to import. To avoid this failure mode, the import hook uses an "opt in" system where C and C++ files can specify they are meant to be used with cppimport by having a comment on the first line that includes the text "cppimport".

rustimport has adopted from this and follows the same pattern. Since rustimport also supports importing whole crates, an additional mechanism was necessary to make that work in the same fashion: You can either create a .rustimport file in the crate's root folder (next to Cargo.toml) or, alternatively, add a # rustimport comment to Cargo.tomls first line.

As an alternative to the import hook, you can use imp or imp_from_path. The rustimport.imp and rustimport.imp_from_path performs exactly the same operation as the import hook but in a slightly more explicit way:

foobar = rustimport.imp("foobar")
foobar = rustimport.imp_from_path("./some/path/foobar.rs")
mycrate = rustimport.imp_from_path("./mycrate/")

By default, these explicit function do not require the "rustimport" keyword on the first line of the .rs source file or the according marker in the crate.

Contributing and architecture

See CONTRIBUTING.md for details on the internals of rustimport and how to get involved in development.

License

rustimport uses the MIT License.

rustimport's People

Contributors

davystrong avatar fzyzcjy avatar mhandb avatar mityax avatar thomasjpfan avatar walnut356 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

rustimport's Issues

Broken build directory when we delete file in the source directory

When we delete files in the sources directory they are kept in the build directory.

It may be problematic in many cases, like this one

  • create a module file "submodule.rs"
  • run some python code which triggers the rust build
  • then change your mind and create a module directory "submodule/mod.rs" and delete the original "submodule.rs"
  • run again your python code => it will fail with this error

error[E0761]: file for module submodule found at both "src\submodule.rs" and "src\submodule\mod.rs"

And the only way to resolve it is to delete the file in the build directory manually for now.

I have a local solution, but I am not sure it is the proper thing to do as I am not familiar enough with the project.
I have modified the _copy_source_to_build_dir function to delete files that no longer exists in the build directory:

    def _copy_source_to_build_dir(self) -> str:
        """
        Copies the source crate or workspace into the temporary build directory
        and return the root path (i.e. to the workspace directory if we're in a
        workspace, to the crate otherwise).
        """

        src_path = self.__workspace_path or self.__crate_path
        output_path = os.path.join(  # e.g. `/output/path/myworkspace` or `/output/path/mycrate` respectively
            self.build_tempdir,
            os.path.basename(self.__workspace_path or self.__crate_path)
        )

        def ignore(dir: str, names: List[str]) -> List[str]:
            if (dir == src_path or dir == output_path) and 'target' in names:
                return ['target']  # do not copy the root "target" folder as it may be huge and slow
            return []

        os.makedirs(output_path, exist_ok=True)

        # Track the copied files so we can delete files that are no longer in the source directory:
        copied_files = set()
        def copy_function(src, dst):
            shutil.copy2(src, dst)
            copied_files.add(dst)

        shutil.copytree(src_path, output_path, ignore=ignore, copy_function=copy_function, dirs_exist_ok=True)

        # Delete files that are no longer in the source directory:
        for root, dirs, files in os.walk(output_path, topdown=True):
            # Remove ignored directories from the walk
            dirs[:] = [d for d in dirs if d not in ignore(root, dirs)]
            for file in files:
                abs_path = os.path.join(root, file)
                if abs_path not in copied_files:
                    os.remove(abs_path)

        return output_path

I also wonder why we need a copy of the source for caching and not use the existing source directory.

Enhancement: Jupyter support

It should be possible to use rustimport to create a Jupyter integration that would allow having cells written in Rust.

The actual logic when executing a cell would be:

  1. Write to a random file.
  2. Compile the extension.
  3. Import the extension.
  4. Do the the equivalent of from new_random_named_extension import * so all the newly defined functions and classes are loaded into the same namespace as the rest of the Jupyter code.

multiple crates in the same workspace cannot reuse the same cache

Hi thanks for the helpful library! However, seems that multiple crates in the same workspace should cannot the same cache. For example, I have crate A and crate B both inside workspace W. Then, I use rustimport and import both A and B in my python code. Then, seems that the two are compiled separately, thus it both takes twice the CPU and twice the disk. It would be great if A and B can reuse the same cache, just like what it is done in standard rust cargo workspaces.

Warning message crashes release build

Running python -m rustimport build --release, crashes if it detects a potential importable crate because the warning (notify_potential_failure_reason) attempts to split None. This happens because, unlike when importing a package, the fullname argument is left undefined. I could submit a PR but I'm not sure what the best approach is. Should I only show the message if fullname is defined or should I show a different, more generic message? Since python -m rustimport build --release searches for all possible matches, showing any warning risks producing a lot of unnecessary warnings, so I would probably just leave out potential matches.

This happens in the try_create method of CrateImportable, and presumably would also happen in the try_create method of SingleFileImportable.

Suggestion: Add release submodule for import_hook

I would like a better way of requesting release builds of Rust code directly within Python. I know I can import rustimport.settings, then set compile_release_binaries to True, and import rustimport.import_hook, but that's annoying. Could a new submodule be added so that I can simply run import rustimport.release.import_hook and it would set compile_release_binaries for me? I can write the PR and change add it to the README if you agree, but I realise this may be somewhat unorthodox.

share cache for different directories

Hi thanks for the lib! My use case is like this: I have some python+rust code (say inside folder_a). Then, whenever I need to execute it (each execution takes e.g. several days, because it is deep learning), I will (use a script to) copy folder_a to something like folder_202201040001, and then execute python script there. In other words, each execution will create one new folder.

Then the problem is, rustimport seems not to reuse cache in this case. Instead, for each different folder, it considers it as a brand new thing and re-compile the whole rust code, which is time/cpu/disk consuming. Thus, is it possible to share cache between folders? Thanks

Enhancement: Add support for --target-cpu=native

For local development, or just fixed hardware, it would be nice to take advantage of the fact that one is not distributing the binaries but compiling them locally. Being able to add --target-cpu=native to RUSTFLAGS would make some code even faster.

This is particularly helpful in numeric code where you can get the compiler to use SIMD automatically, making rustimport a stronger alternative to Numba, which already takes advantage of current CPU features by default.

It's unclear whether this should be on by default. Certainly you want it off anytime you are building on one machine and running the resulting code an arbitrary different machine, because you might end up generating code that won't run on other CPUs with different instruction sets (e.g. not all x86-64 CPUs have AVX-512 or AVX2).

support of mac with m1 pro chip

Hi there, I tried to follow the example in README and got following error:

ImportError: dlopen(/Users/xxx/for-rustimport/playground/lib.cpython-39-darwin.so, 0x0002): tried: '/Users/xxx/for-rustimport/playground/lib.cpython-39-darwin.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')), '/System/Volumes/Preboot/Cryptexes/OS/Users/xxx/for-rustimport/playground/lib.cpython-39-darwin.so' (no such file), '/Users/xxx/for-rustimport/playground/lib.cpython-39-darwin.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64'))

Does rustimport support m1 pro chip? Thanks.

My env:
Macbook Pro 14, macOS 13.4.1
python 3.11
venv

Differing function call requirements between languages with pyfunction

Calling a function can cause compilation errors in python while fixing them in rust or vice versa. This is unintuitive and takes away from how seamless pyfunction is. It also prevents the user from testing the code in rust forcing them to test in python.

eg: The following code will run with cargo run however not when run with a python file

#[pyfunction]
fn test() {
   let window = WindowDesc::new(build_app())
}```

While this code will run with a python file but not with cargo

```rust
#[pyfunction]
fn test() {
   let window = WindowDesc::new(build_app)
}

The difference is weather or not the function is called.

Something that is interesting to note is rust loses deep type inference in this situation with this set up

Screenshot 2024-02-19 at 2 14 59 AM Screenshot 2024-02-19 at 2 15 59 AM

The final thing I found that is probably a different issue was some imports just disappeared when using the python version. Eg

use druid::widget::AspectRatioBox can't be imported into python saying that it doesn't exist however it can be used in the Rust version. This is a very specific error as it doesn't happen with other rust druid::widget::foos. It does however also happen with druid::theme::TextColor

Screenshot 2024-02-19 at 2 28 03 AM

Implement Apple codesign awareness

From @zifeo

Also, you may want to include some code signing logic (codesign --verify --verbose --sign - native.cpython-310-darwin.so) . Currently, it seems that the generated .so is rewritten violating its signature when used by another process (you can also remove and recreate the file instead).

See previous discussion in #2

latest pyo3 compatability

warning: use of deprecated method pyo3::deprecations::GilRefs::<T>::function_arg: use &Bound<'_, T> instead for this function argument
--> polars_ext/src/lib.rs:28:28
|
28 | fn polars_ext(_py: Python, m: &PyModule) -> PyResult<()> {
| ^
|
= note: #[warn(deprecated)] on by default

do not copy `target` folder (which is huge and thus slow)

Hi thanks for the lib! My usage (on my local dev computer) is as follows:

  1. run cargo test etc
  2. also use rustimport and thus use in python code

However, it seems that the rustimport tries to copy all folders, including the huge target folder of my workspace, into the temporary dir. Since the target folder is more than a few GB, it is slow to copy and wastes disk and time.

PS. A stack trace when I realize rustimport is stuck and hit keyboard interrupt is as follows. As can be seen, it seems to be copying.

^CTraceback (most recent call last):
  File "/Users/tom/opt/anaconda3/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/Users/tom/opt/anaconda3/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/tom/Main/research/code/research_mono/research_mono/benchmarks/benchmark_ineq_generator.py", line 17, in <module>
    main()
  File "/Users/tom/Main/research/code/research_mono/research_mono/benchmarks/benchmark_ineq_generator.py", line 9, in main
    g = wrapped_generator.GeneratorVec(hypotheses=hypotheses, num_generators=num_generators)
  File "/Users/tom/Main/research/code/research_mono/research_mono/neural_theorem_prover/interactive_theorem_prover/ineq/generator/wrapped_generator.py", line 8, in __init__
    import rust.ineq
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 664, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 627, in _load_backward_compatible
  File "/Users/tom/opt/anaconda3/lib/python3.9/site-packages/rustimport/import_hook.py", line 48, in load_module
    self.__importable.build(release=settings.compile_release_binaries)
  File "/Users/tom/opt/anaconda3/lib/python3.9/site-packages/rustimport/importable.py", line 206, in build
    shutil.copytree(self.__workspace_path or self.__crate_path, root_output_path, dirs_exist_ok=True)
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 566, in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 504, in _copytree
    copytree(srcobj, dstname, symlinks, ignore, copy_function,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 566, in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 504, in _copytree
    copytree(srcobj, dstname, symlinks, ignore, copy_function,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 566, in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 504, in _copytree
    copytree(srcobj, dstname, symlinks, ignore, copy_function,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 566, in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 508, in _copytree
    copy_function(srcobj, dstname)
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 444, in copy2
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 270, in copyfile
    _fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
  File "/Users/tom/opt/anaconda3/lib/python3.9/shutil.py", line 105, in _fastcopy_fcopyfile
    posix._fcopyfile(infd, outfd, flags)
KeyboardInterrupt

Production ready?

Hi thanks for the crate - I wonder whether it is ready for production or is still under development or alpha?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.