Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python script flask sample #116

Merged
merged 11 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
_packagemanagement
*.py
requirements.txt
fable_modules
25 changes: 25 additions & 0 deletions examples/scripts/README.md
Original file line number Diff line number Diff line change
@@ -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
88 changes: 88 additions & 0 deletions examples/scripts/flask_sample.fsx
Original file line number Diff line number Diff line change
@@ -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="[email protected]"
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

228 changes: 228 additions & 0 deletions examples/scripts/run_flask_app.fsx
Original file line number Diff line number Diff line change
@@ -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