Skip to content

Commit

Permalink
Python script flask sample (#116)
Browse files Browse the repository at this point in the history
* not working

* python script sample not working flask

* fixed script working

* added runner script

* readme

* remove folder

* revert readme

* added gitignore for py files

* remove app.py

* change script

* change readme
  • Loading branch information
jkone27 authored Apr 6, 2024
1 parent b7cfb66 commit c43a0f2
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 0 deletions.
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

0 comments on commit c43a0f2

Please sign in to comment.