From 4d7eeea223d5c853c392ca074ca6f359d0048ce0 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 14:25:00 -0400 Subject: [PATCH 01/27] Launch REPL with `python -m juliacall` --- pysrc/juliacall/__main__.py | 24 +++++++++++++ pysrc/juliacall/banner.jl | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 pysrc/juliacall/__main__.py create mode 100644 pysrc/juliacall/banner.jl diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py new file mode 100644 index 00000000..39522f0f --- /dev/null +++ b/pysrc/juliacall/__main__.py @@ -0,0 +1,24 @@ +from juliacall import Main + +RED = "\033[1;31m" +GREEN = "\033[1;32m" +RESET = "\033[0m" + +import os +path_to_banner = os.path.join(os.path.dirname(__file__), "banner.jl") +Main.seval(f"include(\"{path_to_banner}\"); banner()") + +while True: + try: + line = input(f"{GREEN}juliacall> {RESET}") + except EOFError: + break + if not line.strip(): + continue + try: + result = Main.seval(line) + if result is not None: + print(result) + except Exception as e: + print(f"{RED}ERROR:{RESET} {e}") + diff --git a/pysrc/juliacall/banner.jl b/pysrc/juliacall/banner.jl new file mode 100644 index 00000000..a14e7637 --- /dev/null +++ b/pysrc/juliacall/banner.jl @@ -0,0 +1,72 @@ +# https://github.com/JuliaLang/julia/blob/fae0d0ad3e5d9804533435fe81f4eaac819895af/stdlib/REPL/src/REPL.jl#L1727C1-L1795C4 + +function banner(io::IO = stdout; short = false) + if Base.GIT_VERSION_INFO.tagged_commit + commit_string = Base.TAGGED_RELEASE_BANNER + elseif isempty(Base.GIT_VERSION_INFO.commit) + commit_string = "" + else + days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24))) + days = max(0, days) + unit = days == 1 ? "day" : "days" + distance = Base.GIT_VERSION_INFO.fork_master_distance + commit = Base.GIT_VERSION_INFO.commit_short + + if distance == 0 + commit_string = "Commit $(commit) ($(days) $(unit) old master)" + else + branch = Base.GIT_VERSION_INFO.branch + commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))" + end + end + + commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))" + + if get(io, :color, false)::Bool + c = Base.text_colors + tx = c[:normal] # text + jl = c[:normal] # julia + jc = c[:blue] # juliacall + d1 = c[:bold] * c[:blue] # first dot + d2 = c[:bold] * c[:red] # second dot + d3 = c[:bold] * c[:green] # third dot + d4 = c[:bold] * c[:magenta] # fourth dot + + if short + print(io,""" + $(d3)o$(tx) | Version $(VERSION)PythonCall: $(PythonCall.VERSION) + $(d2)o$(tx) $(d4)o$(tx) | $(commit_string) + """) + else + print(io,""" $(d3)_$(tx) + $(d1)_$(tx) $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx)$(jc) _ _ $(tx) | Documentation: https://juliapy.github.io/PythonCall.jl/ + $(d1)(_)$(jl) | $(d2)(_)$(tx) $(d4)(_)$(tx)$(jc) | || |$(tx) | + $(jl)_ _ _| |_ __ _$(jc) ___ __ _ | || |$(tx) | Julia: $(VERSION) + $(jl)| | | | | | |/ _` |$(jc)/ __|/ _` || || |$(tx) | PythonCall: $(PythonCall.VERSION) + $(jl)| | |_| | | | (_| |$(jc) |__ (_| || || |$(tx) | + $(jl)_/ |\\__'_|_|_|\\__'_|$(jc)\\___|\\__'_||_||_|$(tx) | This REPL is running via Python using JuliaCall. + $(jl)|__/$(tx) | Only basic features are supported. + + """) + end + else + if short + print(io,""" + o | Version $(VERSION)PythonCall: $(PythonCall.VERSION) + o o | $(commit_string) + """) + else + print(io,""" + _ + _ _ _(_)_ _ _ | Documentation: https://juliapy.github.io/PythonCall.jl/ + (_) | (_) (_) | || | | + _ _ _| |_ __ _ ___ __ _ | || | | Julia: $(VERSION) + | | | | | | |/ _` |/ __|/ _` || || | | PythonCall: $(PythonCall.VERSION) + | | |_| | | | (_| | |__ (_| || || | | + _/ |\\__'_|_|_|\\__'_|\\___|\\__'_||_||_| | This REPL is running via Python using JuliaCall. + |__/ | Only basic features are supported. + + """) + end + end +end \ No newline at end of file From 21108f3567510207dc8a3d55b903d5e0ecbc0a8b Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 14:25:11 -0400 Subject: [PATCH 02/27] Init Julia with `python -m juliacall.init` --- pysrc/juliacall/init.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pysrc/juliacall/init.py diff --git a/pysrc/juliacall/init.py b/pysrc/juliacall/init.py new file mode 100644 index 00000000..d1312b13 --- /dev/null +++ b/pysrc/juliacall/init.py @@ -0,0 +1,10 @@ +import juliacall as _ # calls init() which calls juliapkg.executable() which lazily downloads julia + +import sys +if "--debug" in sys.argv: + import juliapkg, json + state = juliapkg.state.STATE + state["version"] = str(state["version"]) + print(json.dumps(state, indent=2)) +else: + print("Initialized successfully. Pass --debug to see the full JuliaPkg state.") \ No newline at end of file From c9cdad89bd4968e85e6757ce9682aaa1ad2b320c Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 14:25:18 -0400 Subject: [PATCH 03/27] Colorize errors --- pysrc/juliacall/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index 0e5ae6e9..bf245885 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -36,9 +36,10 @@ def __str__(self): e = self.exception b = self.backtrace if b is None: - return Base.sprint(Base.showerror, e) + f = Main.seval("e -> io -> showerror(IOContext(io, :color=>true), e)")(e) else: - return Base.sprint(Base.showerror, e, b) + f = Main.seval("(e, b) -> io -> showerror(IOContext(io, :color=>true), e, b)")(e, b) + return Base.sprint(f) @property def exception(self): return self.args[0] From 0fb60595aadcffdefc603e3b1770b7d776001e14 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 15:43:56 -0400 Subject: [PATCH 04/27] Handle `KeyboardInterrupt` in REPL --- pysrc/juliacall/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 39522f0f..dd2ae651 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -11,6 +11,9 @@ while True: try: line = input(f"{GREEN}juliacall> {RESET}") + except KeyboardInterrupt: + print("\n") + continue except EOFError: break if not line.strip(): @@ -18,7 +21,8 @@ try: result = Main.seval(line) if result is not None: - print(result) + Main.display(result) + print() except Exception as e: print(f"{RED}ERROR:{RESET} {e}") From cbe0537c64aa9064398db2c1ec6596fdeb0c58c6 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 15:48:35 -0400 Subject: [PATCH 05/27] More closely match Julia's REPL --- pysrc/juliacall/__main__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index dd2ae651..94cb384e 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -16,13 +16,12 @@ continue except EOFError: break - if not line.strip(): - continue - try: - result = Main.seval(line) - if result is not None: - Main.display(result) - print() - except Exception as e: - print(f"{RED}ERROR:{RESET} {e}") + if sline := line.strip(): + try: + result = Main.seval(sline) + if result is not None: + Main.display(result) + except Exception as e: + print(f"{RED}ERROR:{RESET} {e}") + print() From be3897b92bc3f6d2f52b79ff57809dcf836e432b Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 16:53:34 -0400 Subject: [PATCH 06/27] Use `Base.run_main_repl` --- pysrc/juliacall/__main__.py | 39 +++++++++++++++---------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 94cb384e..609faf53 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -1,27 +1,20 @@ -from juliacall import Main +import os +os.environ.setdefault("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes") +if os.environ.get("PYTHON_JULIACALL_HANDLE_SIGNALS") != "yes": + print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") + exit(1) -RED = "\033[1;31m" -GREEN = "\033[1;32m" -RESET = "\033[0m" +from juliacall import Main -import os -path_to_banner = os.path.join(os.path.dirname(__file__), "banner.jl") -Main.seval(f"include(\"{path_to_banner}\"); banner()") +Main.seval(f"""\ +Base.is_interactive = true -while True: - try: - line = input(f"{GREEN}juliacall> {RESET}") - except KeyboardInterrupt: - print("\n") - continue - except EOFError: - break - if sline := line.strip(): - try: - result = Main.seval(sline) - if result is not None: - Main.display(result) - except Exception as e: - print(f"{RED}ERROR:{RESET} {e}") - print() +include(\"{os.path.join(os.path.dirname(__file__), 'banner.jl')}\") +banner() +if VERSION > v"v1.11.0-alpha1" + Base.run_main_repl(true, false, :no, true, true) +else # interactive, quiet, banner, history_file, color_set + Base.run_main_repl(true, false, false, true, true) +end +""") From eb357e6fc1353f1a12f479d15f7e169376aa9990 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 17:06:57 -0400 Subject: [PATCH 07/27] Update copy --- pysrc/juliacall/banner.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pysrc/juliacall/banner.jl b/pysrc/juliacall/banner.jl index a14e7637..1596c221 100644 --- a/pysrc/juliacall/banner.jl +++ b/pysrc/juliacall/banner.jl @@ -44,8 +44,8 @@ function banner(io::IO = stdout; short = false) $(jl)_ _ _| |_ __ _$(jc) ___ __ _ | || |$(tx) | Julia: $(VERSION) $(jl)| | | | | | |/ _` |$(jc)/ __|/ _` || || |$(tx) | PythonCall: $(PythonCall.VERSION) $(jl)| | |_| | | | (_| |$(jc) |__ (_| || || |$(tx) | - $(jl)_/ |\\__'_|_|_|\\__'_|$(jc)\\___|\\__'_||_||_|$(tx) | This REPL is running via Python using JuliaCall. - $(jl)|__/$(tx) | Only basic features are supported. + $(jl)_/ |\\__'_|_|_|\\__'_|$(jc)\\___|\\__'_||_||_|$(tx) | The JuliaCall REPL is experimental. + $(jl)|__/$(tx) | """) end @@ -63,9 +63,8 @@ function banner(io::IO = stdout; short = false) _ _ _| |_ __ _ ___ __ _ | || | | Julia: $(VERSION) | | | | | | |/ _` |/ __|/ _` || || | | PythonCall: $(PythonCall.VERSION) | | |_| | | | (_| | |__ (_| || || | | - _/ |\\__'_|_|_|\\__'_|\\___|\\__'_||_||_| | This REPL is running via Python using JuliaCall. - |__/ | Only basic features are supported. - + _/ |\\__'_|_|_|\\__'_|\\___|\\__'_||_||_| | The JuliaCall REPL is experimental. + |__/ | """) end end From 64be410ff77d1962be5da3779a8acf3090271547 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 17:09:19 -0400 Subject: [PATCH 08/27] Revert error color change --- pysrc/juliacall/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index bf245885..0e5ae6e9 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -36,10 +36,9 @@ def __str__(self): e = self.exception b = self.backtrace if b is None: - f = Main.seval("e -> io -> showerror(IOContext(io, :color=>true), e)")(e) + return Base.sprint(Base.showerror, e) else: - f = Main.seval("(e, b) -> io -> showerror(IOContext(io, :color=>true), e, b)")(e, b) - return Base.sprint(f) + return Base.sprint(Base.showerror, e, b) @property def exception(self): return self.args[0] From c8df2e788e2579c75d79cd7f522264572ecc946b Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 17:54:30 -0400 Subject: [PATCH 09/27] Refactor --- pysrc/juliacall/__init__.py | 1 + pysrc/juliacall/__main__.py | 21 ++++++++++--------- pysrc/juliacall/banner.jl | 40 ++++++++++++++++--------------------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index 0e5ae6e9..401fa028 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -147,6 +147,7 @@ def args_from_config(config): CONFIG['opt_handle_signals'] = choice('handle_signals', ['yes', 'no'])[0] CONFIG['opt_startup_file'] = choice('startup_file', ['yes', 'no'])[0] CONFIG['opt_heap_size_hint'] = option('heap_size_hint')[0] + CONFIG['opt_banner'] = choice('banner', ['yes', 'no', 'short'])[0] # Stop if we already initialised if CONFIG['inited']: diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 609faf53..de22fc6a 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -4,17 +4,16 @@ print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") exit(1) -from juliacall import Main +from juliacall import Main, Base -Main.seval(f"""\ -Base.is_interactive = true +Base.is_interactive = True -include(\"{os.path.join(os.path.dirname(__file__), 'banner.jl')}\") -banner() +Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) +Main.__PythonCall_banner() -if VERSION > v"v1.11.0-alpha1" - Base.run_main_repl(true, false, :no, true, true) -else # interactive, quiet, banner, history_file, color_set - Base.run_main_repl(true, false, false, true, true) -end -""") +if Main.seval(r'VERSION > v"v1.11.0-alpha1"'): + no_banner_opt = Base.Symbol("no") +else: + no_banner_opt = False + +Base.run_main_repl(True, False, no_banner_opt, True, True) diff --git a/pysrc/juliacall/banner.jl b/pysrc/juliacall/banner.jl index 1596c221..4e4c7712 100644 --- a/pysrc/juliacall/banner.jl +++ b/pysrc/juliacall/banner.jl @@ -1,26 +1,20 @@ # https://github.com/JuliaLang/julia/blob/fae0d0ad3e5d9804533435fe81f4eaac819895af/stdlib/REPL/src/REPL.jl#L1727C1-L1795C4 -function banner(io::IO = stdout; short = false) - if Base.GIT_VERSION_INFO.tagged_commit - commit_string = Base.TAGGED_RELEASE_BANNER - elseif isempty(Base.GIT_VERSION_INFO.commit) - commit_string = "" - else - days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24))) - days = max(0, days) - unit = days == 1 ? "day" : "days" - distance = Base.GIT_VERSION_INFO.fork_master_distance - commit = Base.GIT_VERSION_INFO.commit_short +function __PythonCall_banner(io::IO = stdout) + banner_opt = begin + opts = Base.JLOptions() + b = opts.banner + auto = b == -1 + b == 0 || (auto && !interactiveinput) ? :no : + b == 1 || (auto && interactiveinput) ? :yes : + :short # b == 2 + end - if distance == 0 - commit_string = "Commit $(commit) ($(days) $(unit) old master)" - else - branch = Base.GIT_VERSION_INFO.branch - commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))" - end + if banner_opt == :no + return end - commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))" + short = banner_opt == :short if get(io, :color, false)::Bool c = Base.text_colors @@ -34,8 +28,8 @@ function banner(io::IO = stdout; short = false) if short print(io,""" - $(d3)o$(tx) | Version $(VERSION)PythonCall: $(PythonCall.VERSION) - $(d2)o$(tx) $(d4)o$(tx) | $(commit_string) + $(d3)o$(tx) | Julia $(VERSION) + $(d2)o$(tx) $(d4)o$(tx) $(c[:bold] * jc)o$(tx) | PythonCall $(PythonCall.VERSION) """) else print(io,""" $(d3)_$(tx) @@ -52,8 +46,8 @@ function banner(io::IO = stdout; short = false) else if short print(io,""" - o | Version $(VERSION)PythonCall: $(PythonCall.VERSION) - o o | $(commit_string) + o | Julia $(VERSION) + o o o | PythonCall $(PythonCall.VERSION) """) else print(io,""" @@ -68,4 +62,4 @@ function banner(io::IO = stdout; short = false) """) end end -end \ No newline at end of file +end From a60c530265de5ee61b8668cd579c95f8932cbdb0 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 18:02:08 -0400 Subject: [PATCH 10/27] Fix banner logic --- pysrc/juliacall/banner.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysrc/juliacall/banner.jl b/pysrc/juliacall/banner.jl index 4e4c7712..88b49acd 100644 --- a/pysrc/juliacall/banner.jl +++ b/pysrc/juliacall/banner.jl @@ -4,9 +4,9 @@ function __PythonCall_banner(io::IO = stdout) banner_opt = begin opts = Base.JLOptions() b = opts.banner - auto = b == -1 - b == 0 || (auto && !interactiveinput) ? :no : - b == 1 || (auto && interactiveinput) ? :yes : + b == -1 ? :yes : + b == 0 ? :no : + b == 1 ? :yes : :short # b == 2 end From ceae6c72ced84f4b4bd866a0620b26a1c41eda7e Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 18:08:33 -0400 Subject: [PATCH 11/27] Fix short banner colors --- pysrc/juliacall/banner.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pysrc/juliacall/banner.jl b/pysrc/juliacall/banner.jl index 88b49acd..1a6d210b 100644 --- a/pysrc/juliacall/banner.jl +++ b/pysrc/juliacall/banner.jl @@ -20,16 +20,18 @@ function __PythonCall_banner(io::IO = stdout) c = Base.text_colors tx = c[:normal] # text jl = c[:normal] # julia - jc = c[:blue] # juliacall + jc = c[:blue] # juliacall text + jb = c[:bold] * jc # bold blue dot d1 = c[:bold] * c[:blue] # first dot d2 = c[:bold] * c[:red] # second dot d3 = c[:bold] * c[:green] # third dot d4 = c[:bold] * c[:magenta] # fourth dot + d5 = c[:bold] * c[:yellow] # bold yellow dot if short print(io,""" - $(d3)o$(tx) | Julia $(VERSION) - $(d2)o$(tx) $(d4)o$(tx) $(c[:bold] * jc)o$(tx) | PythonCall $(PythonCall.VERSION) + $(jb)o$(tx) | Julia $(VERSION) + $(jb)o$(tx) $(d5)o$(tx) | PythonCall $(PythonCall.VERSION) """) else print(io,""" $(d3)_$(tx) @@ -46,8 +48,8 @@ function __PythonCall_banner(io::IO = stdout) else if short print(io,""" - o | Julia $(VERSION) - o o o | PythonCall $(PythonCall.VERSION) + o | Julia $(VERSION) + o o | PythonCall $(PythonCall.VERSION) """) else print(io,""" From f79928d72072cf1c466d0912d0719d782fcca18b Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 18:40:42 -0400 Subject: [PATCH 12/27] Refactor config to argparse --- pysrc/juliacall/__init__.py | 1 - pysrc/juliacall/__main__.py | 25 ++++++++++++++++++++++--- pysrc/juliacall/banner.jl | 11 ++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index 401fa028..0e5ae6e9 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -147,7 +147,6 @@ def args_from_config(config): CONFIG['opt_handle_signals'] = choice('handle_signals', ['yes', 'no'])[0] CONFIG['opt_startup_file'] = choice('startup_file', ['yes', 'no'])[0] CONFIG['opt_heap_size_hint'] = option('heap_size_hint')[0] - CONFIG['opt_banner'] = choice('banner', ['yes', 'no', 'short'])[0] # Stop if we already initialised if CONFIG['inited']: diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index de22fc6a..93b8dcec 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -4,16 +4,35 @@ print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") exit(1) +import argparse +from pathlib import Path +parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") +parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') +parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') +parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') +parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') +args = parser.parse_args() + from juliacall import Main, Base Base.is_interactive = True -Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) -Main.__PythonCall_banner() +if not args.quiet: + Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) + Main.__PythonCall_banner(Base.Symbol(args.banner)) if Main.seval(r'VERSION > v"v1.11.0-alpha1"'): no_banner_opt = Base.Symbol("no") else: no_banner_opt = False -Base.run_main_repl(True, False, no_banner_opt, True, True) +if args.preamble: + Main.include(str(args.preamble.resolve())) + +Base.run_main_repl( + Base.is_interactive, + args.quiet, + no_banner_opt, + args.history_file == 'yes', + True +) diff --git a/pysrc/juliacall/banner.jl b/pysrc/juliacall/banner.jl index 1a6d210b..82c4093a 100644 --- a/pysrc/juliacall/banner.jl +++ b/pysrc/juliacall/banner.jl @@ -1,14 +1,7 @@ # https://github.com/JuliaLang/julia/blob/fae0d0ad3e5d9804533435fe81f4eaac819895af/stdlib/REPL/src/REPL.jl#L1727C1-L1795C4 -function __PythonCall_banner(io::IO = stdout) - banner_opt = begin - opts = Base.JLOptions() - b = opts.banner - b == -1 ? :yes : - b == 0 ? :no : - b == 1 ? :yes : - :short # b == 2 - end +function __PythonCall_banner(banner_opt::Symbol = :yes) + io = stdout if banner_opt == :no return From 698e16e881e2f70347452f80b2e62c373ef5f24b Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 19:11:23 -0400 Subject: [PATCH 13/27] Refactor --- pyproject.toml | 3 +++ pysrc/juliacall/__main__.py | 40 +++-------------------------------- pysrc/juliacall/repl.py | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 pysrc/juliacall/repl.py diff --git a/pyproject.toml b/pyproject.toml index a2bd772a..557d6188 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,3 +26,6 @@ dev = [ [tool.hatch.build.targets.wheel] packages = ["pysrc/juliacall"] + +[project.scripts] +pyjulia = "juliacall.repl:run_repl" \ No newline at end of file diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 93b8dcec..70426e4f 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -1,38 +1,4 @@ -import os -os.environ.setdefault("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes") -if os.environ.get("PYTHON_JULIACALL_HANDLE_SIGNALS") != "yes": - print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") - exit(1) +from juliacall.repl import run_repl -import argparse -from pathlib import Path -parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") -parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') -parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') -parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') -parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') -args = parser.parse_args() - -from juliacall import Main, Base - -Base.is_interactive = True - -if not args.quiet: - Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) - Main.__PythonCall_banner(Base.Symbol(args.banner)) - -if Main.seval(r'VERSION > v"v1.11.0-alpha1"'): - no_banner_opt = Base.Symbol("no") -else: - no_banner_opt = False - -if args.preamble: - Main.include(str(args.preamble.resolve())) - -Base.run_main_repl( - Base.is_interactive, - args.quiet, - no_banner_opt, - args.history_file == 'yes', - True -) +if __name__ == '__main__': + run_repl() diff --git a/pysrc/juliacall/repl.py b/pysrc/juliacall/repl.py new file mode 100644 index 00000000..8f0bfab1 --- /dev/null +++ b/pysrc/juliacall/repl.py @@ -0,0 +1,42 @@ +def run_repl(): + import os + os.environ.setdefault("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes") + if os.environ.get("PYTHON_JULIACALL_HANDLE_SIGNALS") != "yes": + print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") + exit(1) + + import argparse + from pathlib import Path + parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") + parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') + parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') + parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') + parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') + args = parser.parse_args() + + from juliacall import Main, Base + + Base.is_interactive = True + + if not args.quiet: + Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) + Main.__PythonCall_banner(Base.Symbol(args.banner)) + + if Main.seval(r'VERSION > v"v1.11.0-alpha1"'): + no_banner_opt = Base.Symbol("no") + else: + no_banner_opt = False + + if args.preamble: + Main.include(str(args.preamble.resolve())) + + Base.run_main_repl( + Base.is_interactive, + args.quiet, + no_banner_opt, + args.history_file == 'yes', + True + ) + +if __name__ == '__main__': + run_repl() From 121b36e749e11d17db1965015af65d28b7d8f75d Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 19:14:30 -0400 Subject: [PATCH 14/27] Add name guard to init --- pysrc/juliacall/init.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pysrc/juliacall/init.py b/pysrc/juliacall/init.py index d1312b13..a34481c8 100644 --- a/pysrc/juliacall/init.py +++ b/pysrc/juliacall/init.py @@ -1,10 +1,11 @@ -import juliacall as _ # calls init() which calls juliapkg.executable() which lazily downloads julia +if __name__ == '__main__': + import juliacall as _ # calls init() which calls juliapkg.executable() which lazily downloads julia -import sys -if "--debug" in sys.argv: - import juliapkg, json - state = juliapkg.state.STATE - state["version"] = str(state["version"]) - print(json.dumps(state, indent=2)) -else: - print("Initialized successfully. Pass --debug to see the full JuliaPkg state.") \ No newline at end of file + import sys + if "--debug" in sys.argv: + import juliapkg, json + state = juliapkg.state.STATE + state["version"] = str(state["version"]) + print(json.dumps(state, indent=2)) + else: + print("Initialized successfully. Pass --debug to see the full JuliaPkg state.") \ No newline at end of file From 1fa8b9876a12d5346d923b12cd1d0d63bf43e8bc Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 19:24:20 -0400 Subject: [PATCH 15/27] `pyjulia` -> `juliapy` --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 557d6188..d3dcb0e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,4 +28,4 @@ dev = [ packages = ["pysrc/juliacall"] [project.scripts] -pyjulia = "juliacall.repl:run_repl" \ No newline at end of file +juliapy = "juliacall.repl:run_repl" \ No newline at end of file From bd2c6cb0d329e025844c2b290e8b43a923a38019 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 19:30:27 -0400 Subject: [PATCH 16/27] Remove script --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d3dcb0e5..a2bd772a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,3 @@ dev = [ [tool.hatch.build.targets.wheel] packages = ["pysrc/juliacall"] - -[project.scripts] -juliapy = "juliacall.repl:run_repl" \ No newline at end of file From 6acaebabad0e827c4f79531cdaa8e24dd7b109ca Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 21:19:33 -0400 Subject: [PATCH 17/27] Add `-e`/`--eval` and refactor argparse --- pysrc/juliacall/__main__.py | 24 +++++++++++++++++++++--- pysrc/juliacall/repl.py | 23 +++++++---------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 70426e4f..cc9692b3 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -1,4 +1,22 @@ -from juliacall.repl import run_repl - if __name__ == '__main__': - run_repl() + import argparse + from pathlib import Path + parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") + parser.add_argument('-e', '--eval', type=str, default=None, help='Evaluate . If specified, all other arguments are ignored.') + + parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') + parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') + parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') + parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') + args = parser.parse_args() + if args.eval is not None: + from juliacall import Main + Main.seval(args.eval) + else: + from juliacall.repl import run_repl + run_repl( + banner=args.banner, + quiet=args.quiet, + history_file=args.history_file, + preamble=args.preamble + ) diff --git a/pysrc/juliacall/repl.py b/pysrc/juliacall/repl.py index 8f0bfab1..bc297db2 100644 --- a/pysrc/juliacall/repl.py +++ b/pysrc/juliacall/repl.py @@ -1,40 +1,31 @@ -def run_repl(): +def run_repl(banner='yes', quiet=False, history_file='yes', preamble=None): import os os.environ.setdefault("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes") if os.environ.get("PYTHON_JULIACALL_HANDLE_SIGNALS") != "yes": print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") exit(1) - import argparse - from pathlib import Path - parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") - parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') - parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') - parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') - parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') - args = parser.parse_args() - from juliacall import Main, Base Base.is_interactive = True - if not args.quiet: + if not quiet: Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) - Main.__PythonCall_banner(Base.Symbol(args.banner)) + Main.__PythonCall_banner(Base.Symbol(banner)) if Main.seval(r'VERSION > v"v1.11.0-alpha1"'): no_banner_opt = Base.Symbol("no") else: no_banner_opt = False - if args.preamble: - Main.include(str(args.preamble.resolve())) + if preamble: + Main.include(str(preamble.resolve())) Base.run_main_repl( Base.is_interactive, - args.quiet, + quiet, no_banner_opt, - args.history_file == 'yes', + history_file == 'yes', True ) From 5070ea10ceddb69a7b1a820784a4f6933cabfc25 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 21:21:52 -0400 Subject: [PATCH 18/27] Add `-E`/`--print` --- pysrc/juliacall/__main__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index cc9692b3..86329f99 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -3,15 +3,21 @@ from pathlib import Path parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") parser.add_argument('-e', '--eval', type=str, default=None, help='Evaluate . If specified, all other arguments are ignored.') + parser.add_argument('-E', '--print', type=str, default=None, help='Evaluate and display the result. If specified, all other arguments are ignored.') parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') args = parser.parse_args() + assert not (args.eval is not None and args.print is not None) if args.eval is not None: from juliacall import Main Main.seval(args.eval) + elif args.print is not None: + from juliacall import Main + result = Main.seval(args.print) + Main.display(result) else: from juliacall.repl import run_repl run_repl( From 972f47f595f5ac0eb0c3c42c3f0709e93853bcd4 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 21:22:26 -0400 Subject: [PATCH 19/27] Add message for `-e` and `-E` assert --- pysrc/juliacall/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 86329f99..f7cdd667 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -10,7 +10,7 @@ parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') args = parser.parse_args() - assert not (args.eval is not None and args.print is not None) + assert not (args.eval is not None and args.print is not None), "Cannot specify both -e/--eval and -E/--print" if args.eval is not None: from juliacall import Main Main.seval(args.eval) From 846d82351689b21e91b7bea346b0d8d1259cdf59 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 21:43:31 -0400 Subject: [PATCH 20/27] Allow kwargs for `juliacall.repl` api --- pysrc/juliacall/__main__.py | 9 ++++----- pysrc/juliacall/repl.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index f7cdd667..339c4684 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -1,16 +1,15 @@ if __name__ == '__main__': import argparse - from pathlib import Path parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") parser.add_argument('-e', '--eval', type=str, default=None, help='Evaluate . If specified, all other arguments are ignored.') parser.add_argument('-E', '--print', type=str, default=None, help='Evaluate and display the result. If specified, all other arguments are ignored.') - parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') - parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') - parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') - parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') + from juliacall.repl import add_repl_args + add_repl_args(parser) + args = parser.parse_args() assert not (args.eval is not None and args.print is not None), "Cannot specify both -e/--eval and -E/--print" + if args.eval is not None: from juliacall import Main Main.seval(args.eval) diff --git a/pysrc/juliacall/repl.py b/pysrc/juliacall/repl.py index bc297db2..7c43e2fa 100644 --- a/pysrc/juliacall/repl.py +++ b/pysrc/juliacall/repl.py @@ -29,5 +29,17 @@ def run_repl(banner='yes', quiet=False, history_file='yes', preamble=None): True ) +def add_repl_args(parser): + from pathlib import Path + parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') + parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') + parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') + parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') + if __name__ == '__main__': - run_repl() + import argparse + parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") + from juliacall.repl import add_repl_args + add_repl_args(parser) + args = parser.parse_args() + run_repl(args.banner, args.quiet, args.history_file, args.preamble) From 6631df6e5aa0c72ef10fe914715b23fa4734755a Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 21:49:10 -0400 Subject: [PATCH 21/27] Clean up imports --- pysrc/juliacall/__main__.py | 11 ++++++----- pysrc/juliacall/init.py | 9 ++++++--- pysrc/juliacall/repl.py | 12 ++++++------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index 339c4684..dc3c95e7 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -1,24 +1,25 @@ +import argparse + +from juliacall import Main +from juliacall.repl import run_repl, add_repl_args + + if __name__ == '__main__': - import argparse parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") parser.add_argument('-e', '--eval', type=str, default=None, help='Evaluate . If specified, all other arguments are ignored.') parser.add_argument('-E', '--print', type=str, default=None, help='Evaluate and display the result. If specified, all other arguments are ignored.') - from juliacall.repl import add_repl_args add_repl_args(parser) args = parser.parse_args() assert not (args.eval is not None and args.print is not None), "Cannot specify both -e/--eval and -E/--print" if args.eval is not None: - from juliacall import Main Main.seval(args.eval) elif args.print is not None: - from juliacall import Main result = Main.seval(args.print) Main.display(result) else: - from juliacall.repl import run_repl run_repl( banner=args.banner, quiet=args.quiet, diff --git a/pysrc/juliacall/init.py b/pysrc/juliacall/init.py index a34481c8..7af7bb92 100644 --- a/pysrc/juliacall/init.py +++ b/pysrc/juliacall/init.py @@ -1,9 +1,12 @@ +import json +import juliapkg +import sys + if __name__ == '__main__': - import juliacall as _ # calls init() which calls juliapkg.executable() which lazily downloads julia + # invoking python -m juliacall.init automatically imports juliacall which + # calls init() which calls juliapkg.executable() which lazily downloads julia - import sys if "--debug" in sys.argv: - import juliapkg, json state = juliapkg.state.STATE state["version"] = str(state["version"]) print(json.dumps(state, indent=2)) diff --git a/pysrc/juliacall/repl.py b/pysrc/juliacall/repl.py index 7c43e2fa..077be779 100644 --- a/pysrc/juliacall/repl.py +++ b/pysrc/juliacall/repl.py @@ -1,12 +1,15 @@ +import argparse +import os +from pathlib import Path + +from juliacall import Main, Base + def run_repl(banner='yes', quiet=False, history_file='yes', preamble=None): - import os os.environ.setdefault("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes") if os.environ.get("PYTHON_JULIACALL_HANDLE_SIGNALS") != "yes": print("Experimental JuliaCall REPL requires PYTHON_JULIACALL_HANDLE_SIGNALS=yes") exit(1) - from juliacall import Main, Base - Base.is_interactive = True if not quiet: @@ -30,16 +33,13 @@ def run_repl(banner='yes', quiet=False, history_file='yes', preamble=None): ) def add_repl_args(parser): - from pathlib import Path parser.add_argument('--banner', choices=['yes', 'no', 'short'], default='yes', help='Enable or disable startup banner') parser.add_argument('--quiet', '-q', action='store_true', help='Quiet startup: no banner, suppress REPL warnings') parser.add_argument('--history-file', choices=['yes', 'no'], default='yes', help='Load or save history') parser.add_argument('--preamble', type=Path, help='Code to be included before the REPL starts') if __name__ == '__main__': - import argparse parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") - from juliacall.repl import add_repl_args add_repl_args(parser) args = parser.parse_args() run_repl(args.banner, args.quiet, args.history_file, args.preamble) From 8939efa82272bbf9958ddb5d14a9930e91911dd9 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Tue, 22 Jul 2025 21:51:33 -0400 Subject: [PATCH 22/27] Make `main` function in `__main__` --- pysrc/juliacall/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pysrc/juliacall/__main__.py b/pysrc/juliacall/__main__.py index dc3c95e7..4590e975 100644 --- a/pysrc/juliacall/__main__.py +++ b/pysrc/juliacall/__main__.py @@ -4,7 +4,7 @@ from juliacall.repl import run_repl, add_repl_args -if __name__ == '__main__': +def main(): parser = argparse.ArgumentParser("JuliaCall REPL (experimental)") parser.add_argument('-e', '--eval', type=str, default=None, help='Evaluate . If specified, all other arguments are ignored.') parser.add_argument('-E', '--print', type=str, default=None, help='Evaluate and display the result. If specified, all other arguments are ignored.') @@ -26,3 +26,6 @@ history_file=args.history_file, preamble=args.preamble ) + +if __name__ == '__main__': + main() From 64bf6b5d176d19fbc8f91f3045731b920c95c83a Mon Sep 17 00:00:00 2001 From: Michael Klamkin Date: Mon, 28 Jul 2025 17:38:32 -0400 Subject: [PATCH 23/27] Update pysrc/juliacall/repl.py --- pysrc/juliacall/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysrc/juliacall/repl.py b/pysrc/juliacall/repl.py index 077be779..bdd120ce 100644 --- a/pysrc/juliacall/repl.py +++ b/pysrc/juliacall/repl.py @@ -16,7 +16,7 @@ def run_repl(banner='yes', quiet=False, history_file='yes', preamble=None): Main.include(os.path.join(os.path.dirname(__file__), 'banner.jl')) Main.__PythonCall_banner(Base.Symbol(banner)) - if Main.seval(r'VERSION > v"v1.11.0-alpha1"'): + if Main.seval(r'VERSION ≥ v"v1.11.0-alpha1"'): no_banner_opt = Base.Symbol("no") else: no_banner_opt = False From 33a9c67470a765611bc543cbf84ef5b646317a03 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Mon, 28 Jul 2025 18:33:26 -0400 Subject: [PATCH 24/27] Add test --- pytest/test_all.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pytest/test_all.py b/pytest/test_all.py index 9cdc8ce4..5cb0d583 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -168,3 +168,45 @@ def test_call_nogil(yld, raw): t2 = time() - t0 # executing the tasks should take about 1 second because they happen in parallel assert 0.9 < t2 < 1.5 + + +def test_repl(): + import sys + import tomllib + import juliapkg + import juliacall as _ + import subprocess + import time + + jl_version = juliapkg.state.STATE["version"] + + # grab PythonCall.jl version from pyproject.toml + with open("pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + pythoncall_version = pyproject["project"]["version"] + + cmd = [sys.executable, '-m', 'juliacall'] + process = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1 + ) + output = "" + timestart = time.time() + while time.time() - timestart < 100: + char = process.stdout.read(1) + if not char: + break + output += char + if output.endswith("julia>"): + break + assert f"Julia: {jl_version}" in output + assert f"PythonCall: {pythoncall_version}" in output + assert "julia>" in output + process.stdin.write('\x04') # Ctrl+D + process.stdin.flush() + process.wait() + assert process.returncode == 0 From 66eedaea8c4f0c674ceecdede48059efe555c44d Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Mon, 28 Jul 2025 18:37:41 -0400 Subject: [PATCH 25/27] Don't check PythonCall.jl version --- pytest/test_all.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pytest/test_all.py b/pytest/test_all.py index 5cb0d583..633831e1 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -172,7 +172,6 @@ def test_call_nogil(yld, raw): def test_repl(): import sys - import tomllib import juliapkg import juliacall as _ import subprocess @@ -180,11 +179,6 @@ def test_repl(): jl_version = juliapkg.state.STATE["version"] - # grab PythonCall.jl version from pyproject.toml - with open("pyproject.toml", "rb") as f: - pyproject = tomllib.load(f) - pythoncall_version = pyproject["project"]["version"] - cmd = [sys.executable, '-m', 'juliacall'] process = subprocess.Popen( cmd, @@ -204,7 +198,6 @@ def test_repl(): if output.endswith("julia>"): break assert f"Julia: {jl_version}" in output - assert f"PythonCall: {pythoncall_version}" in output assert "julia>" in output process.stdin.write('\x04') # Ctrl+D process.stdin.flush() From c83c309e6ed246aadc7266aa04e177ba67bead8d Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Mon, 28 Jul 2025 19:27:02 -0400 Subject: [PATCH 26/27] Simplify test --- pytest/test_all.py | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/pytest/test_all.py b/pytest/test_all.py index 633831e1..b7edea67 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -171,35 +171,17 @@ def test_call_nogil(yld, raw): def test_repl(): - import sys + import sys, subprocess import juliapkg import juliacall as _ - import subprocess - import time - jl_version = juliapkg.state.STATE["version"] - - cmd = [sys.executable, '-m', 'juliacall'] - process = subprocess.Popen( - cmd, + output, _ = subprocess.Popen( + [sys.executable, "-m", "juliacall"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1 - ) - output = "" - timestart = time.time() - while time.time() - timestart < 100: - char = process.stdout.read(1) - if not char: - break - output += char - if output.endswith("julia>"): - break - assert f"Julia: {jl_version}" in output + stderr=subprocess.STDOUT, + text=True + ).communicate(input="", timeout=10) + + assert f"Julia: {juliapkg.state.STATE["version"]}" in output assert "julia>" in output - process.stdin.write('\x04') # Ctrl+D - process.stdin.flush() - process.wait() - assert process.returncode == 0 From 6e60d4c77290809b27f1a9a39b724ceada242a08 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Mon, 28 Jul 2025 19:29:01 -0400 Subject: [PATCH 27/27] Fix string in fstring for 3.9 --- pytest/test_all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest/test_all.py b/pytest/test_all.py index b7edea67..93305600 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -183,5 +183,5 @@ def test_repl(): text=True ).communicate(input="", timeout=10) - assert f"Julia: {juliapkg.state.STATE["version"]}" in output + assert f"Julia: {juliapkg.state.STATE['version']}" in output assert "julia>" in output