mirror of
https://github.com/kennethreitz/replit-py.git
synced 2026-06-05 23:10:18 +00:00
@@ -1,7 +1,9 @@
|
||||
[flake8]
|
||||
select = ANN,B,B9,BLK,C,D,DAR,E,F,I,S,W
|
||||
ignore = E203,W503,ANN101,ANN102,S322
|
||||
per-file-ignores = src/replit/__init__.py:F401
|
||||
per-file-ignores =
|
||||
src/replit/__init__.py:F401
|
||||
src/replit/maqpi/__init__.py:F401
|
||||
max-line-length = 88
|
||||
application-import-names = vidgen,tests
|
||||
import-order-style = google
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
"""A basic example of repl auth."""
|
||||
import simple
|
||||
|
||||
app = simple.App("app")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
if simple.signed_in:
|
||||
return "Hello " + simple.auth.name
|
||||
else:
|
||||
return simple.signin() # optionally: simple.sigin(title="My title")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
Generated
+54
-3
@@ -91,7 +91,7 @@ python-versions = "*"
|
||||
version = "3.0.4"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
category = "main"
|
||||
description = "Composable command line interface toolkit"
|
||||
name = "click"
|
||||
optional = false
|
||||
@@ -218,6 +218,25 @@ version = "1.0.2"
|
||||
[package.dependencies]
|
||||
flake8 = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A simple framework for building complex web applications."
|
||||
name = "flask"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "1.1.2"
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.10.1"
|
||||
Werkzeug = ">=0.15"
|
||||
click = ">=5.1"
|
||||
itsdangerous = ">=0.24"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Git Object Database"
|
||||
@@ -257,7 +276,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.2.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
category = "main"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
name = "itsdangerous"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.1.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A very fast and expressive template engine."
|
||||
name = "jinja2"
|
||||
optional = false
|
||||
@@ -271,7 +298,7 @@ MarkupSafe = ">=0.23"
|
||||
i18n = ["Babel (>=0.8)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
category = "main"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
name = "markupsafe"
|
||||
optional = false
|
||||
@@ -602,6 +629,18 @@ brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
name = "werkzeug"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
||||
watchdog = ["watchdog"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "7388d8240a2817695e502fb845922da8df699d77d143693ffe3285eca1df3495"
|
||||
python-versions = "^3.8"
|
||||
@@ -685,6 +724,10 @@ flake8-polyfill = [
|
||||
{file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
|
||||
{file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
|
||||
{file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"},
|
||||
]
|
||||
gitdb = [
|
||||
{file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"},
|
||||
{file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"},
|
||||
@@ -701,6 +744,10 @@ imagesize = [
|
||||
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
|
||||
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
|
||||
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
|
||||
@@ -908,3 +955,7 @@ urllib3 = [
|
||||
{file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
|
||||
{file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
||||
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
|
||||
]
|
||||
|
||||
@@ -9,6 +9,7 @@ license = "MIT"
|
||||
python = "^3.8"
|
||||
requests = "^2.24.0"
|
||||
typing_extensions = "^3.7.4"
|
||||
flask = "^1.1.2"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^3.8.3"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""The replit python module."""
|
||||
from . import maqpi
|
||||
from .audio import Audio
|
||||
from .database import db
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Make apps quickly in python."""
|
||||
import os
|
||||
|
||||
import flask
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from . import html
|
||||
from .app import App
|
||||
from .html import Page, Paragraph
|
||||
from .utils import sign_in_snippet, signin
|
||||
from ..database import db
|
||||
|
||||
auth = LocalProxy(lambda: flask.request.auth)
|
||||
signed_in = LocalProxy(lambda: flask.request.signed_in)
|
||||
|
||||
# TODO: signinwall(exclude=['/a', '/b'])
|
||||
# TODO: @need_signin
|
||||
# TODO: Param checking with @needs_params
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Core of maqpi."""
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReplitAuthContext:
|
||||
"""A dataclass defining a Repl Auth state."""
|
||||
|
||||
user_id: int
|
||||
name: str
|
||||
roles: str
|
||||
|
||||
@classmethod
|
||||
def from_headers(cls, headers: dict):
|
||||
"""Initialize an instance using the Replit magic headers.
|
||||
|
||||
Args:
|
||||
headers (dict): A dictionary of headers received
|
||||
|
||||
Returns:
|
||||
[type]: An initialized class instance
|
||||
"""
|
||||
return cls(
|
||||
user_id=headers.get("X-Replit-User-Id"),
|
||||
name=headers.get("X-Replit-User-Name"),
|
||||
roles=headers.get("X-Replit-User-Roles"),
|
||||
)
|
||||
|
||||
@property
|
||||
def signed_in(self) -> bool:
|
||||
"""Return whether or not the authentication is activated."""
|
||||
return self.name != ""
|
||||
|
||||
|
||||
class Request(flask.Request):
|
||||
"""Represents a client request."""
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Initialize request and run update_auth."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.update_auth()
|
||||
|
||||
def update_auth(self) -> None:
|
||||
"""Update the auth property to be a ReplitAuthContext."""
|
||||
self.auth = ReplitAuthContext.from_headers(self.headers)
|
||||
|
||||
@property
|
||||
def signed_in(self) -> bool:
|
||||
"""Return whether or not the authentication is activated."""
|
||||
return self.auth.signed_in
|
||||
|
||||
|
||||
class App(flask.Flask):
|
||||
"""Represents a web application."""
|
||||
|
||||
request_class = Request
|
||||
|
||||
def all_pages_sign_in(self) -> None:
|
||||
"""Require sign-in on all pages."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
||||
"""Interface with the underlying flask instance's run function."""
|
||||
return super().run(*args, **kwargs)
|
||||
|
||||
def run(self, port: int = 8080, localhost: bool = False) -> None:
|
||||
"""Run the app.
|
||||
|
||||
Args:
|
||||
port (int): The port to run the app on. Defaults to 8080.
|
||||
localhost (bool): Whether to run the app without exposing it on all
|
||||
interfaces. Defaults to False.
|
||||
"""
|
||||
super().run(host="localhost" if localhost else "0.0.0.0", port=port)
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Utitilities for interacting with static files."""
|
||||
import flask
|
||||
|
||||
|
||||
class FileCache:
|
||||
"""A simple cache for files."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.data = {}
|
||||
|
||||
def add_to_cache(self, filename: str, content: str) -> None:
|
||||
"""Add a filename to the cache.
|
||||
|
||||
Args:
|
||||
filename (str): The filename to add.
|
||||
content (str): The content to add to the cache.
|
||||
"""
|
||||
self.data[filename] = content
|
||||
|
||||
def has(self, filename: str) -> bool:
|
||||
"""Whether the cache has a certain filename.
|
||||
|
||||
Args:
|
||||
filename (str): The filename to check for.
|
||||
|
||||
Returns:
|
||||
bool: Whether the cache has filename.
|
||||
"""
|
||||
return filename in self.data
|
||||
|
||||
def invalidate(self, filename: str) -> None:
|
||||
"""Remove a filename from the cache.
|
||||
|
||||
Args:
|
||||
filename (str): The filename to remove.
|
||||
"""
|
||||
try:
|
||||
self.data.pop(filename)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def flush(self) -> None:
|
||||
"""Remove all values from the cache."""
|
||||
self.data = {}
|
||||
|
||||
|
||||
cache = FileCache()
|
||||
|
||||
|
||||
class File(flask.Response):
|
||||
"""Represents a static file."""
|
||||
|
||||
def __init__(self, filename: str, no_cache: bool = False) -> None:
|
||||
self.filename = str(filename)
|
||||
self.no_cache = no_cache
|
||||
|
||||
# load file
|
||||
if filename not in cache:
|
||||
with open(filename, "r") as f:
|
||||
self.content = f.read()
|
||||
|
||||
if not no_cache:
|
||||
cache.add_to_cache(filename, self.content)
|
||||
|
||||
super().__init__(self.content)
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Class-representations for HTML elements."""
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
class HTMLElement(ABC):
|
||||
"""Base class for an HTML element."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Paragraph:
|
||||
"""Represents a Paragraph (p) tag."""
|
||||
|
||||
content: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"<p>{self.content}</p>"
|
||||
|
||||
|
||||
class Page(flask.Response):
|
||||
"""Represents an HTML page."""
|
||||
|
||||
def __init__(self, title: str = None, head: str = "", body: str = "") -> None:
|
||||
self.title = title
|
||||
self.head = head
|
||||
self.body = body
|
||||
|
||||
title_html = f"<title>{self.title}</title>\n " if self.title else ""
|
||||
super().__init__(
|
||||
f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{title_html}{self.head}
|
||||
</head>
|
||||
<body>
|
||||
{self.body}
|
||||
</body>
|
||||
</html>"""
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Utitilities to make development easier."""
|
||||
from .html import Page
|
||||
|
||||
|
||||
sign_in_snippet = (
|
||||
'<script authed="location.reload()" '
|
||||
'src="https://auth.turbio.repl.co/script.js"></script>'
|
||||
)
|
||||
|
||||
|
||||
def signin(title: str = "Please Sign In") -> Page:
|
||||
"""Return a sign-in page.
|
||||
|
||||
Args:
|
||||
title (str): The title of the sign in page. Defaults to "Please Sign In".
|
||||
|
||||
Returns:
|
||||
Page: The sign-in page.
|
||||
"""
|
||||
return Page(title=title, body=sign_in_snippet)
|
||||
Reference in New Issue
Block a user