diff --git a/examples/scripts/.gitignore b/examples/scripts/.gitignore new file mode 100644 index 0000000..5da186f --- /dev/null +++ b/examples/scripts/.gitignore @@ -0,0 +1,4 @@ +_packagemanagement +*.py +requirements.txt +fable_modules \ No newline at end of file diff --git a/examples/scripts/README.md b/examples/scripts/README.md new file mode 100644 index 0000000..911377f --- /dev/null +++ b/examples/scripts/README.md @@ -0,0 +1,25 @@ +# Compile and Run a Python Script + +``` +dotnet tool restore +``` + +then run + +``` +dotnet fsi run_flask_app.fsx flask_sample.fsx +``` + +this will run using `pip-run` for now requirements.txt manual file is supported but inference via `pipreqs` is not working as expected. + +## python dependencies + +* python3 +* pip +* `pipx` > required, used to install global python packages. DEPENDENCY, [install instructions here](https://pipx.pypa.io/stable/) or for mac via brew. +* `pipreqs` for package requiremnts inference : `pipx install pipreqs` will be executed. (not working yet) +* `pip-run` for running with defualt one shot option, which runs your script one time in a temp env (can be installed with pipx), `pipx install pip-run` will be executed + +## pip-run + +* [pip-run](https://github.com/jaraco/pip-run/blob/main/README.rst) is used to run all script in "isolation" taking care of creatinv venv and installing libs in temp dirs and removing them after execution \ No newline at end of file diff --git a/examples/scripts/flask_sample.fsx b/examples/scripts/flask_sample.fsx new file mode 100644 index 0000000..db8ddd7 --- /dev/null +++ b/examples/scripts/flask_sample.fsx @@ -0,0 +1,88 @@ +#r "nuget: Fable.Core" +#r "nuget: Fable.Python" +#r "nuget: Feliz.ViewEngine" +#r "nuget: Zanaptak.TypedCssClasses" +// #r "nuget: Fli" +open Fable.Python.Builtins +open Feliz.ViewEngine +open Zanaptak.TypedCssClasses + +// PIP: flask +open Fable.Python.Flask + +type Bulma = CssClasses<"https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.3/css/bulma.min.css", Naming.PascalCase> + +module View = + let title (str: string) = Html.p [ prop.classes [ Bulma.Title ]; prop.text str ] + let subTitle (str: string) = Html.p [ prop.classes [ Bulma.Subtitle ]; prop.text str ] + + let model = {| + Title="Fable Python |> F# ♥️ Python" + Description="Demo Website, Fable Python running on Flask!" + Banner="https://unsplash.it/1200/900?random" + PermaLink="https://fable.io" + Author="dag@brattli.net" + Brand="public/favicon.png" + |} + + let head = + Html.head [ + Html.title [ prop.text model.Title ] + + Html.meta [ prop.charset.utf8 ] + Html.meta [ prop.name "author"; prop.content model.Author ] + Html.meta [ prop.name "description"; prop.content model.Description ] + + Html.meta [ prop.httpEquiv.contentType; prop.content "text/html"; prop.charset.utf8 ] + Html.meta [ prop.name "viewport"; prop.content "width=device-width, initial-scale=1" ] + + Html.meta [ + prop.custom ("http-equiv", "Cache-Control") + prop.content "no-cache, no-store, must-revalidate" + ] + Html.meta [ prop.custom ("http-equiv", "Pragma"); prop.content "no-cache" ] + Html.meta [ prop.custom ("http-equiv", "Expires"); prop.content "0" ] + + Html.link [ prop.rel "icon"; prop.href "public/favicon.ico" ] + Html.link [ prop.rel "stylesheet"; prop.href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.3/css/bulma.min.css"; prop.crossOrigin.anonymous ] + ] + + let body = Html.div [ + title model.Title + subTitle model.Description + ] + + let section = + Html.section [ + prop.classes [ Bulma.Hero; Bulma.IsFullheightWithNavbar ] + prop.style [ + style.backgroundImageUrl (model.Banner) + style.backgroundPosition "center" + style.backgroundSize.cover + ] + prop.children [ + Html.div [ + prop.classes [ Bulma.HeroBody; Bulma.IsDark ] + prop.children [ Html.div [ prop.classes [ Bulma.Container ]; prop.children body ] ] + ] + ] + ] + + let html = + Html.html [ + head + Html.body [ + section + ] + ] + +let renderView () = + View.html |> Render.htmlDocument + +// NB: this must not be inside a module for Flask to resolve the app correctly! +// https://stackoverflow.com/questions/57718786/error-launching-flask-app-with-error-failed-to-find-flask-application +let app = Flask.Create(__name__, "/public") + +// Setup the routes. See if we can use attributes instead +app.route("/")(renderView) |> ignore + diff --git a/examples/scripts/run_flask_app.fsx b/examples/scripts/run_flask_app.fsx new file mode 100644 index 0000000..9d9320e --- /dev/null +++ b/examples/scripts/run_flask_app.fsx @@ -0,0 +1,228 @@ +// dotnet fsi run_flask_app flask_sample.fsx [--global](to uses pip-run and venv by default) +#r "nuget: Fli" +#r "nuget: EluciusFTW.SpectreCoff" +//we can use this package to run python commands together with the script +open Fli +open SpectreCoff + +"Python + F#" |> figlet |> toConsole + +// using vscode F# highlight ext can also execute inline python +let python (p : string)= p + +let create_py_venv () = + cli { + Shell Shells.BASH + Command ("python3 -m venv .venv") + } + |> Command.execute + |> Output.printText + +let install_pip_run_package() = + "install pip-run" |> C |> toConsole + cli { + Shell Shells.BASH + Command ("pipx install pip-run") + } + |> Command.execute + |> Output.printText + + +let isGlobal = + match fsi.CommandLineArgs |> Seq.toList with + |_::_::"--global"::[] -> true + |_::"--global"::_ -> true + |_ -> false + +let install_pipreqs_package() = + "install pipreqs" |> C |> toConsole + cli { + Shell Shells.BASH + Command ("pipx install pipreqs") + } + |> Command.execute + |> Output.printText + +// FAILS... +let gen_pip_requirements_file() = + "execute pipreqs to generate requirements.txt" |> C |> toConsole + cli { + Shell Shells.BASH + Command ("python3 -m pipreqs .") + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let create_requirements() = + cli { + Shell Shells.BASH + Command ("touch requirements.txt") + } + |> Command.execute + |> Output.printText + +let gen_pip_requirements_file_local() = + "execute local pipreqs to generate requirements.txt" |> C |> toConsole + cli { + Shell Shells.BASH + Command ("pipreqs .") + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let install_pip_requirements() = + "pip install requirements.txt" |> C |> toConsole + cli { + Shell Shells.BASH + Command ("python3 -m pip install -r requirements.txt ") + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let activate_py_venv () = + cli { + Shell Shells.BASH + Command ("source .venv/bin/activate") + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let which_python () = + cli { + Shell Shells.BASH + Command ("which python") + } + |> Command.execute + |> Output.printText + + +/// pip install flask +let pip_install_flask() = + cli { + Shell Shells.BASH + Command ("python3 -m pip install flask") + } + |> Command.execute + |> Output.printText + +/// pip install extra dependencies if needed, specify them as extra arg ',' separated +let pip_install_extras() = + let dependencies = + match fsi.CommandLineArgs |> Seq.toList with + |_::_::trd::[] -> trd.Split(",") + |_ -> [||] + + for dep in dependencies do + cli { + Shell Shells.BASH + Command ($"python3 -m pip install {dep}") + } + |> Command.execute + |> Output.printText + +let fable_compile_flask_app() = + "FABLE" |> figlet |> toConsole + + let flaskScriptName = + printfn $"args: {fsi.CommandLineArgs}" + match fsi.CommandLineArgs |> Seq.toList with + |_::snd::[] -> snd + |_ -> "app.fsx" + + cli { + Shell Shells.BASH + Command ($"dotnet tool restore && dotnet fable {flaskScriptName} --lang Python --noCache") + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let remove_old_app() = + "rm app.py" |> C |> toConsole + cli { + Shell Shells.BASH + Command "rm app.py" + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let rename_and_cleanup() = + "rename latest script to app.py" |> C |> toConsole + cli { + Shell Shells.BASH + Command "mv *.py app.py" + } + |> Command.execute + |> Output.throwIfErrored + |> Output.printText + +let deactivate_py_venv() = + cli { + Shell Shells.BASH + Command ("python3 -m deactivate") + } + |> Command.execute + |> Output.printText + +let run_flask_app() = + try + "starting app to listen on http://127.0.0.1:5000" |> P |> toConsole + cli { + Shell Shells.BASH + Command ("python3 -m flask run") + } + |> Command.execute + |> Output.throwIfErrored + |> ignore + with ex -> + printfn $"script exited with code {ex.Message}" + () + +let one_shot_run_flask_app() = + try + "starting ONE-SHOT app to listen on http://127.0.0.1:5000" |> P |> toConsole + cli { + Shell Shells.BASH + Command ("pip-run flask -r requirements.txt -- -m flask run") + } + |> Command.execute + |> Output.throwIfErrored + |> ignore + with ex -> + $"script exited with code {ex.Message}" |> P |> toConsole + () + + +let isVenv = + match fsi.CommandLineArgs |> Seq.toList with + |_::_::"--venv"::[] -> true + |_::"--venv"::_ -> true + |_ -> false + +// execution + +if isVenv then + create_py_venv() + |> activate_py_venv + |> which_python + printfn "remember to run deactivate at the end of the script, or which python" + +//install_pipreqs_package() +//|> if isGlobal then gen_pip_requirements_file else gen_pip_requirements_file_local +() +|> create_requirements +|> if isGlobal then id else install_pip_run_package +|> fable_compile_flask_app +|> remove_old_app +|> rename_and_cleanup +|> if isGlobal then run_flask_app else one_shot_run_flask_app + +// need a strategy to not break on CTRL-C for this... +// maybe use `fg` ? +// |> deactivate_py_venv +// |> which_python \ No newline at end of file