pythonimmediate.engine module
Abstract engine class.
- class pythonimmediate.engine.ChildProcessEngine(engine_name: EngineName, args: Iterable[str] = (), env: Optional[dict[str, str]] = None, autorestart: bool = False, debug_log_communication: Optional[Union[str, Path]] = None, from_dump: bool = False)[source]
Bases:
Engine
An object that represents a \(\TeX\) engine that runs as a subprocess of this process.
Can be used as a context manager to automatically close the subprocess when the context is exited. Alternatively
close()
can be used to manually terminate the subprocess.For example, the following Python code, if run alone, will spawn a \(\TeX\) process and use it to write “Hello world” to a file named
a.txt
in the temporary directory:>>> from pythonimmediate.engine import ChildProcessEngine, default_engine >>> from pythonimmediate import execute >>> from pathlib import Path >>> with ChildProcessEngine("pdftex") as engine, default_engine.set_engine(engine): ... # do something with the engine, for example: ... execute(r''' ... \immediate\openout9=a.txt ... \immediate\write9{Hello world} ... \immediate\closeout9 ... ''') ... (Path(engine.directory)/"a.txt").read_text() 'Hello world\n' >>> # now the engine is closed and the file is cleaned up. >>> (Path(engine.directory)/"a.txt").is_file() False
You can also use this to generate PDF file programmatically:
>>> with ChildProcessEngine("pdftex") as engine, default_engine.set_engine(engine): ... execute(r'\documentclass{article}') ... execute(r'\begin{document}') ... execute(r'Hello world') ... engine.terminate() ... print(engine.read_output_file()[:9]) b'%PDF-1.5\n'
See
DefaultEngine
for how to use the resulting engine object.We attempt to do correct error detection on the Python side by parsing the output/log file:
>>> with ChildProcessEngine("pdftex") as engine, default_engine.set_engine(engine): ... execute(r'\error') Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessError: Undefined control sequence.
However, it’s not always easy. For example the following code will reproduce the output on error identically:
\message{^^J! error.^^J% l.1 \errmessage{error}^^J% ^^J% ? } \readline -1 to \xx
As an alternative to
terminate()
, you can also just execute\end{document}
, but be sure to catchTeXProcessExited
if you do so. If you do this, there’s no need to callterminate()
.>>> with ChildProcessEngine("pdftex") as engine, default_engine.set_engine(engine): ... execute(r'\documentclass{article}') ... execute(r'\begin{document}') ... execute(r'Hello world') ... try: execute(r'\end{document}') ... except TeXProcessExited: pass ... print(engine.read_output_file()[:9]) b'%PDF-1.5\n'
- Parameters
args – List of additional arguments to be passed to the executable, such as
--recorder
etc.env – See documentation of env argument in
subprocess.Popen
. Note that it’s usually recommended to append environment variables that should be set toos.environ
instead of replacing it entirely.from_dump –
Slightly undocumented feature at the moment.
Refer to https://tex.stackexchange.com/a/687427 for explanation. Decompressing the format file is optional, explained in that link.
Example:
>>> from pythonimmediate import BalancedTokenList >>> from pathlib import Path >>> import tempfile >>> import gzip >>> with ChildProcessEngine("pdftex", args=["--ini", "&pdflatex"]) as engine, default_engine.set_engine(engine): ... try: execute(r"\documentclass{article} \usepackage{tikz} \pythonimmediatechildprocessdump") ... except TeXProcessExited: pass ... else: assert False ... f=Path(tempfile.mktemp(suffix=".fmt")) ... _size=f.write_bytes(gzip.decompress(engine.read_output_file("fmt"))) >>> with ChildProcessEngine("pdftex", from_dump=True, args=["&"+str(f.with_suffix(""))]) as engine, default_engine.set_engine(engine): ... BalancedTokenList(r"\the\numexpr 12+34\relax").expand_o() <BalancedTokenList: 4₁₂ 6₁₂> >>> f.unlink()
autorestart –
Mostly for testing purpose – whenever an error happen, the engine will be killed and automatically restarted.
For example:
>>> from pythonimmediate import count >>> engine=ChildProcessEngine("pdftex") >>> with default_engine.set_engine(engine): execute(r'\error') Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessError: Undefined control sequence. >>> with default_engine.set_engine(engine): count[0] Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessError: error already happened
>>> engine=ChildProcessEngine("pdftex", autorestart=True) >>> count[0]=2 >>> with default_engine.set_engine(engine): execute(r'\error') Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessError: Undefined control sequence. >>> with default_engine.set_engine(engine): count[0] 1 >>> with default_engine.set_engine(engine): execute(r'\error') Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessError: Undefined control sequence.
- read_output_file(extension: str = 'pdf') bytes [source]
Read the output file with the given extension.
This is only reliable when the process has already been terminated. Refer to
terminate()
.See
ChildProcessEngine
for an usage example. SeeMultiChildProcessEngine
for another example.
- terminate() None [source]
Terminate the current process.
This must be used in place of
close()
in order to stop the process but still keep the generated files.See
ChildProcessEngine
for an usage example.Similar to
file.close
,subprocess.Popen.wait
orsubprocess.Popen.kill
, this method does nothing if the process is already terminated.>>> from pythonimmediate import execute >>> with ChildProcessEngine("pdftex") as engine, default_engine.set_engine(engine): ... execute(r'\typeout{123}') ... engine.terminate() ... engine.terminate() ... engine.close() >>> engine.terminate() >>> engine.close() >>> engine.close()
- class pythonimmediate.engine.DefaultEngine[source]
Bases:
object
A convenience class that can be used to avoid passing explicit
engine
argument to functions.This is thread-safe, which means that each thread can have its own set default engine and
set_engine()
for one thread does not affect other threads.Users should not instantiate this class directly. Instead, use
default_engine
.Usage example:
default_engine.set_engine(engine) # set only for this thread execute("hello world") # this is executed on engine=engine
See also
- get_engine() Engine [source]
Convenience helper function, return the engine.
All the other functions that use this one (those that make use of the engine) will raise RuntimeError if the engine is None.
- set_engine(engine: Optional[Engine]) _SetDefaultEngineContextManager [source]
Set the default engine to another engine.
Can also be used as a context manager to revert to the original engine. Example:
>>> from pythonimmediate import execute >>> _new_engine=ChildProcessEngine("pdftex") >>> with default_engine.set_engine(_new_engine) as engine: # only for this thread ... assert engine is _new_engine ... assert default_engine.engine is _new_engine ... execute(r"\count0=5") >>> # now the original engine is restored >>> _new_engine.close()
Note that the following form, while allowed, is discouraged because it may cause resource leak (the engine may keeps running even after the block exits depends on whether it’s garbage-collected):
>>> with default_engine.set_engine(ChildProcessEngine("pdftex")): ... pass
It’s recommended to write the following instead, which will close
e
at the end of the block:>>> with ChildProcessEngine("pdftex") as e, default_engine.set_engine(e): ... pass
- class pythonimmediate.engine.Engine[source]
Bases:
ABC
- add_on_close(f: Callable[[Engine], None]) None [source]
Add a function that will be executed when the engine is closed.
This function takes the engine object itself as the only argument to avoid a circular reference issue which would prevent the engine from being garbage-collected.
>>> e=ChildProcessEngine("pdftex") >>> e.add_on_close(lambda _: print(1)) >>> e.add_on_close(lambda _: print(2)) >>> e.close() 1 2
The same engine may be closed multiple times in case of
autorestart
, in that case the function will only be called once.>>> e=ChildProcessEngine("pdftex", autorestart=True) >>> a=[1, 2, 3] >>> e.add_on_close(lambda e: a.pop()) >>> from pythonimmediate import execute >>> with default_engine.set_engine(e): execute(r"\error") Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessError: Undefined control sequence. >>> a [1, 2, 3] >>> e.close() >>> a [1, 2]
- property config: GlobalConfiguration
Self-explanatory.
- property is_unicode: bool
Self-explanatory.
- property name: EngineName
Self-explanatory.
- pythonimmediate.engine.EngineName
The
EngineName
type is a string that specifies the name of the engine.alias of
Literal
[‘pdftex’, ‘xetex’, ‘luatex’]
- class pythonimmediate.engine.EngineStatus(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]
Bases:
Enum
Represent the status of the engine. Helper functions are supposed to manage the status by itself.
running: the engine is running \(\TeX\) code and Python cannot write to it, only wait for Python commands.
waiting: the engine is waiting for Python to write a handler.
error: an error happened.
exited: the engine exited cleanly.
- class pythonimmediate.engine.ParentProcessEngine(pseudo_config: GlobalConfiguration)[source]
Bases:
Engine
Represent the engine if this process is started by the \(\TeX\)’s pythonimmediate library.
This should not be instantiated directly. Only
pythonimmediate.textopy.main()
should instantiate this.
- exception pythonimmediate.engine.TeXProcessExited[source]
Bases:
Exception
An exception that will be raised if some operation makes the process exits.
It is, however, safe to just catch this in case of
ChildProcessEngine
. See there for an example.>>> from pythonimmediate import execute, BalancedTokenList >>> execute(r'\documentclass{article}\begin{document}hello world') >>> execute(r'\end{document}') Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessExited >>> execute(r'\documentclass{article}\begin{document}hello world') >>> BalancedTokenList(r'\end{document}').execute() Traceback (most recent call last): ... pythonimmediate.engine.TeXProcessExited
- pythonimmediate.engine.default_engine = <pythonimmediate.engine.DefaultEngine object>
A constant that can be used to avoid passing explicit
engine
argument to functions.See documentation of
DefaultEngine
for more details.For Python running inside a \(\TeX\) process, useful attributes are
name
andis_unicode
.