Skip to content

Commit

Permalink
Merge pull request #5 from manatlan/feature/new_version_with_namedpipes
Browse files Browse the repository at this point in the history
Feature/new version with namedpipes
  • Loading branch information
manatlan authored May 26, 2024
2 parents 8038734 + ed58a91 commit 8727b1b
Show file tree
Hide file tree
Showing 33 changed files with 769 additions and 1,239 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ dmypy.json
.pyre/

uid_*.ses
poetry.lock
53 changes: 53 additions & 0 deletions .idx/dev.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# To learn more about how to use Nix to configure your environment
# see: https://developers.google.com/idx/guides/customize-idx-env
{ pkgs, ... }: {
# Which nixpkgs channel to use.
channel = "stable-23.11"; # or "unstable"
# Use https://search.nixos.org/packages to find packages
packages = [
# pkgs.go
pkgs.python311
pkgs.poetry
pkgs.killall
# pkgs.python311Packages.pip
# pkgs.nodejs_20
# pkgs.nodePackages.nodemon
];
# Sets environment variables in the workspace
env = {};
idx = {
# Search for the extensions you want on https://open-vsx.org/ and use "publisher.id"
extensions = [
# "vscodevim.vim"
];
# Enable previews
previews = {
enable = true;
previews = {
# web = {
# # Example: run "npm run dev" with PORT set to IDX's defined port for previews,
# # and show it in IDX's web preview panel
# command = ["npm" "run" "dev"];
# manager = "web";
# env = {
# # Environment variables to set for your server
# PORT = "$PORT";
# };
# };
};
};
# Workspace lifecycle hooks
workspace = {
# Runs when a workspace is first created
onCreate = {
# Example: install JS dependencies from NPM
# npm-install = "npm install";
};
# Runs when the workspace is (re)started
onStart = {
# Example: start a background task to watch and re-build backend code
# watch-backend = "npm run watch-backend";
};
};
};
}
Binary file added .idx/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"python.testing.pytestArgs": [
"."
".","-s"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
Expand Down
79 changes: 21 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# htagweb

# htagweb
[![Test](https://github.com/manatlan/htagweb/actions/workflows/on_commit_do_all_unittests.yml/badge.svg)](https://github.com/manatlan/htagweb/actions/workflows/on_commit_do_all_unittests.yml)

<a href="https://pypi.org/project/htagweb/">
Expand All @@ -9,13 +8,8 @@
This module exposes htag's runners for the web. It's the official runners to expose
htag apps on the web, to handle multiple clients/session in the right way.

There are 3 runners:

- **AppServer** : the real one ;-)
- SimpleServer : for tests purposes
- HtagServer : (use SimpleServer) to browse/expose python/htag files in an UI.

## AppServer
## Runner

It's the real runner to expose htag apps in a real production environment, and provide
all features, while maintaining tag instances like classical/desktop htag runners.
Expand All @@ -26,11 +20,11 @@ Process live as long as the server live
**Features**

* based on [starlette](https://pypi.org/project/starlette/)
* use sessions (multiple ways to handle sessions (file, mem, etc ...))
* use session (in file)
* compatible with **uvloop** !!!
* compatible with multiple gunicorn/uvicorn/webworkers !!!
* compatible with [tag.update()](https://manatlan.github.io/htag/tag_update/)
* works on gnu/linux, ios or windows (from py3.7 to py3.11)!
* works on gnu/linux, ios ~~or windows~~ (from py3.7 to py3.11)!
* real starlette session available (in tag.state, and starlette request.session)
* compatible with oauth2 authent ( [authlib](https://pypi.org/project/Authlib/) )
* 'parano mode' (can aes encrypt all communications between client & server ... to avoid mitm'proxies on ws/http interactions)
Expand All @@ -41,16 +35,24 @@ Process live as long as the server live
Like a classical starlette'app :

```python
from htagweb import AppServer
from htagweb import Runner
from yourcode import YourApp # <-- your htag class

app=AppServer( YourApp, ... )
app=Runner( YourApp, ... )
if __name__=="__main__":
app.run()
```

You can use the following parameters :

#### host (str)

The host to bind to. (default is "0.0.0.0")

#### port (int)

The port to bind to. (default is 8000)

#### debug (bool)

- When False: (default) no debugging facilities
Expand Down Expand Up @@ -89,37 +91,13 @@ By default, it's `0` (process lives as long as the server lives).

IMPORTANT : the "tag.update" feature doesn't reset the inactivity timeout !

#### session_factory (htagweb.sessions)

You can provide a Session Factory to handle the session in different modes.

- htagweb.sessions.MemDict (default) : sessions are stored in memory (renewed on reboot)
- htagweb.sessions.FileDict : sessions are stored in filesystem (renewed on reboot)
- htagweb.sessions.FilePersistentDict : make sessions persistent after reboot

## SimpleServer

It's a special runner for tests purposes. It doesn't provide all features (parano mode, ssl, session factory...).
Its main goal is to provide a simple runner during dev process, befause when you hit "F5" :
it will destroy/recreate the tag instances.

SimpleServer uses only websocket interactions (tag instances exist only during websocket connexions)

And it uses `htagweb.sessions.FileDict` as session manager.

## HtagServer

It's a special runner, which is mainly used by the `python3 -m htagweb`, to expose
current python/htag files in a browser. Its main goal is to test quickly the files
whose are in your folder, using an UI in your browser.

It uses the SimpleServer, so it does'nt provide all features (parano mode, ssl, session factory ...)

-------------------------------

## Roadmap / futur


- finnish this version ;-)
- better unittests !!!!!!!!!!!!!!!!
- better logging !!!!!!!!!!!!!!!!
- parano mode : use public/private keys ?
Expand All @@ -134,10 +112,10 @@ from htag import Tag

class App(Tag.div):
def init(self):
self+= "hello world"
self<= "hello world"

from htagweb import AppServer
AppServer( App ).run()
from htagweb import Runner
Runner( App ).run()
```

or, with gunicorn (in a `server.py` file, as following):
Expand All @@ -147,10 +125,10 @@ from htag import Tag

class App(Tag.div):
def init(self):
self+= "hello world"
self<= "hello world"

from htagweb import AppServer
app=AppServer( App )
from htagweb import Runner
app=Runner( App )
```

and run server :
Expand All @@ -165,19 +143,4 @@ See a more advanced example in [examples folder](https://github.com/manatlan/hta
python3 examples/main.py
```

# Standalone module can act as server

The module can act as "development server", providing a way to quickly run any htag class in a browser. And let you browse current `*.py` files in a browser.

```bash
$ python3 -m htagweb
```
htagweb will look for an "index:App" (a file `index.py` (wich contains a htag.Tag subclass 'App').), and if it can't found it : expose its own htag app to let user browse pythons files in the browser (`/!\`)

or

```bash
$ python3 -m htagweb main:App
```
if you want to point the "/" (home path) to a file `main.py` (wich contains a htag.Tag subclass 'App').

14 changes: 9 additions & 5 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def init(self):
self+=self.orendu

def render(self):
self.otitle.set( "Live Session" )
self.orendu.set( json.dumps( dict(self.session.items()), indent=1))
self.otitle.clear( "Live Session" )
self.orendu.clear( json.dumps( dict(self.session.items()), indent=1))

class App(Tag.body):
imports=[TagSession]
Expand Down Expand Up @@ -63,7 +63,7 @@ def clllll(o):
async def loop_timer(self):
while 1:
await asyncio.sleep(0.5)
self.place.set(time.time() )
self.place.clear(time.time() )
if not await self.place.update(): # update component using current websocket
# break if can't (<- good practice to kill this asyncio/loop)
break
Expand All @@ -83,8 +83,12 @@ def test(o):
async def serve(req):
return await req.app.handle(req,Jo,http_only=True,parano=True)
#------------------------------------------------------
from htagweb import SimpleServer,AppServer
app=AppServer( App, timeout_inactivity=0 )
from htagweb import Runner
#/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
# **IMPORTANT** current host serving on SSL
# on your localmachine, switch ssl to False !!!
#/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
app=Runner( App, debug=True, ssl=True )
app.add_route("/jo", serve)

if __name__=="__main__":
Expand Down
23 changes: 12 additions & 11 deletions examples/main.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import os,sys; sys.path.insert(0,os.path.realpath(os.path.dirname(os.path.dirname(__file__))))
import os
from examples import app1
from examples import app2

from htagweb import SimpleServer,AppServer
from htagweb import Runner
from starlette.responses import HTMLResponse

import app1
import app2

#=-
from htag import Tag
class AppKo(Tag.div):
Expand All @@ -28,23 +27,25 @@ async def handlePath(request):
"""
return HTMLResponse(h)
elif p=="a1":
return await app.serve(request, "app1.App")
return await app.serve(request, "examples.app1.App")
elif p=="a12":
return await app.serve(request, "app1:App")
return await app.serve(request, "examples.app1:App")
elif p=="a2":
return await app.serve(request, app2.App )
elif p=="a22":
return await app.serve(request, app2 )
elif p=="k1":
return await app.serve(request, AppKo )
elif p=="k2":
return await app.serve(request, "nimp_module.nimp_name" )
return await app.serve(request, "nimp_module:nimp_name" )
else:
return HTMLResponse("404",404)


#app=SimpleServer()
app=AppServer()
#/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
# **IMPORTANT** current host serving on SSL
# on your localmachine, switch ssl to False !!!
#/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
app=Runner(debug=True,ssl=True)
app.add_route("/{path:path}", handlePath )

if __name__=="__main__":
Expand Down
11 changes: 6 additions & 5 deletions examples/oauth_example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import os,sys; sys.path.insert(0,os.path.realpath(os.path.dirname(os.path.dirname(__file__))))

from htag import Tag
from htagweb import SimpleServer
from htagweb import Runner
from authlib.integrations.starlette_client import OAuth
from starlette.responses import Response,RedirectResponse
import time
Expand Down Expand Up @@ -91,8 +89,11 @@ def render(self): # dynamic rendering

#=========================================

# IT WORKS FOR THE 3 runners of htagweb ;-) (should work with old webhttp/webws runners from htag too)
app=SimpleServer(App)
#/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
# **IMPORTANT** current host serving on SSL
# on your localmachine, switch ssl to False !!!
#/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
app=Runner(App,ssl=True)

app.add_route("/oauth_{action}", oauth_request_action )

Expand Down
2 changes: 1 addition & 1 deletion examples/servemain
Original file line number Diff line number Diff line change
@@ -1 +1 @@
gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b localhost:8000 --preload main:app
gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b localhost:8000 --preload examples.main:app
2 changes: 1 addition & 1 deletion examples/servemain.bat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
uvicorn.exe main:app --workers 4
uvicorn.exe examples.main:app --workers 4
43 changes: 43 additions & 0 deletions examples/simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

# -*- coding: utf-8 -*-

# a classic app, which is runned here with htag.Runner !
# btw this app is used in a htagweb context, thru pytest

from htag import Tag
import asyncio,time

class App(Tag.body):
statics="body {background:#EEE;}"

def init(self):
self.place = Tag.div()
asyncio.ensure_future( self.loop_timer() )
self <= "Hello World"
self <= self.place
self <= Tag.button("exit", _onclick=lambda ev: self.exit())
self <= Tag.button("Say hi", _onclick=self.sayhi)

async def sayhi(self,ev):
self <= "hi!"

async def loop_timer(self):
while 1:
await asyncio.sleep(0.5)
self.place.clear(time.time() )
if not await self.place.update(): # update component using current websocket
# break if can't (<- good practice to kill this asyncio/loop)
break

#=================================================================================
from htag.runners import Runner

if __name__ == "__main__":

# import logging
# logging.basicConfig(format='[%(levelname)-5s] %(name)s: %(message)s',level=logging.INFO)
# logging.getLogger("htag.tag").setLevel( logging.INFO )
# logging.getLogger("htag.render").setLevel( logging.INFO )


Runner(App).run()
9 changes: 4 additions & 5 deletions htagweb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# #############################################################################
# Copyright (C) 2023 manatlan manatlan[at]gmail(dot)com
# Copyright (C) 2024 manatlan manatlan[at]gmail(dot)com
#
# MIT licence
#
Expand All @@ -9,9 +9,8 @@

__version__ = "0.0.0" # auto updated

from .appserver import AppServer
from .simpleserver import SimpleServer # a completly different beast, but compatible with ^^
from .htagserver import HtagServer # a completly different beast.
from .runners import Runner
from .runners import Runner as AppServer # for compatibility with htagweb <=0.24

__all__= ["AppServer","SimpleServer"]

__all__= ["Runner"]
Loading

0 comments on commit 8727b1b

Please sign in to comment.