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

Bundle UI static files with the python package #941

Merged
merged 25 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e33bc56
Add npm build commands to setup.py
allisonking Jul 25, 2022
77c06dd
Update MANIFEST.in to include UI files
allisonking Jul 25, 2022
ef9a359
WIP referencing package UI assets
allisonking Jul 25, 2022
de65cfa
Make sure to nest frontend within src/fidesctl
allisonking Jul 26, 2022
78c40d8
Rework how we grab the static files
allisonking Jul 26, 2022
db59f29
Don't fail when file is a directory
allisonking Jul 26, 2022
622f273
Remove comment
allisonking Jul 26, 2022
db965a8
Merge branch 'main' into aking-940-package-ui
allisonking Jul 26, 2022
d5f0bb3
Build wheel instead of sdist
allisonking Jul 26, 2022
3b16fee
Swap pkgutil for importlib
allisonking Jul 27, 2022
ae3a39d
Build universal wheel
allisonking Jul 27, 2022
552683b
Revert "Add npm build commands to setup.py"
allisonking Jul 27, 2022
104a109
Support building the frontend before wheel
allisonking Jul 27, 2022
32fbb30
Update publish package workflow to build js
allisonking Jul 27, 2022
1fbdf6c
Attempt to trigger package upload
allisonking Jul 27, 2022
ee23f30
Fix working directory
allisonking Jul 27, 2022
5568a72
Remove forced call to publish package
allisonking Jul 27, 2022
90d3299
Pull UI specific files out
allisonking Jul 27, 2022
91d13d9
Update changelog
allisonking Jul 27, 2022
47de39e
Merge branch 'main' into aking-940-package-ui
allisonking Jul 27, 2022
96f7d51
Revert testpypi repository arg
allisonking Jul 27, 2022
e6bf8e1
Remove unused const
allisonking Jul 27, 2022
0a557c5
Update .dockerignore
allisonking Jul 28, 2022
d7836dc
Fix docker references
allisonking Jul 28, 2022
65d24be
Merge branch 'main' into aking-940-package-ui
allisonking Jul 28, 2022
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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ include dev-requirements.txt
include versioneer.py
include src/fidesctl/api/ctl/alembic.ini
include src/fidesctl/templates/fides_datamap_template.xlsx
recursive-include src/ui-build *
allisonking marked this conversation as resolved.
Show resolved Hide resolved
46 changes: 44 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
import pathlib
import subprocess
from distutils.cmd import Command

import versioneer
from setuptools import find_packages, setup
from setuptools.command.build_py import build_py


class NPMExportCommand(Command):
"""Custom command to export our frontend UI"""

description = "build the UI via npm"
user_options = [("client-name=", None, "name of client to export")]

def initialize_options(self) -> None:
"""Set default values for options. We only have the admin-ui
right now, so set that as the default."""
self.client_name = "admin-ui"

def finalize_options(self) -> None:
"""Post-process options"""
return

def run(self) -> None:
"""Run npm export"""
directory = f"clients/{self.client_name}"
install_command = "npm install"
build_command = "npm run prod-export"
subprocess.check_call(install_command.split(" "), cwd=directory)
subprocess.check_call(build_command.split(" "), cwd=directory)


class BuildPyCommand(build_py):
"""Extend the default build_py command to also call our custom npm export command"""

def run(self) -> None:
self.run_command("npm_export")
build_py.run(self)


here = pathlib.Path(__file__).parent.resolve()
long_description = open("README.md").read()
Expand Down Expand Up @@ -36,15 +72,21 @@
[value for key, value in extras.items() if key not in dangerous_extras], []
)

versioneer_cmdclass = versioneer.get_cmdclass()
npm_export_cmdclass = {"npm_export": NPMExportCommand}
build_py_cmdclass = {"build_py": BuildPyCommand}

setup(
name="fidesctl",
version=versioneer.get_version(),
cmdclass=versioneer.get_cmdclass(),
cmdclass={**versioneer_cmdclass, **npm_export_cmdclass, **build_py_cmdclass},
description="CLI for Fides",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/ethyca/fides",
entry_points={"console_scripts": ["fidesctl=fidesctl.cli:cli","fides=fidesctl.cli:cli"]},
entry_points={
"console_scripts": ["fidesctl=fidesctl.cli:cli", "fides=fidesctl.cli:cli"]
},
python_requires=">=3.8, <4",
package_dir={"": "src"},
packages=find_packages(where="src"),
Expand Down
44 changes: 28 additions & 16 deletions src/fidesctl/api/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""
Contains the code that sets up the API.
"""
import pkgutil
allisonking marked this conversation as resolved.
Show resolved Hide resolved
from datetime import datetime
from logging import WARNING
from pathlib import Path
from typing import Callable
from typing import Callable, Optional

from fastapi import FastAPI, HTTPException, Request, Response, status
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from fideslib.oauth.api.deps import get_config as lib_get_config
from fideslib.oauth.api.deps import get_db as lib_get_db
from fideslib.oauth.api.deps import verify_oauth_client as lib_verify_oauth_client
Expand Down Expand Up @@ -65,16 +65,26 @@ def configure_routes() -> None:
app.dependency_overrides[lib_verify_oauth_client] = verify_oauth_client


@app.on_event("startup")
async def create_webapp_dir_if_not_exists() -> None:
"""Creates the webapp directory if it doesn't exist."""
def get_ui_file(path: Path) -> Optional[Path]:
"""There are 3 cases to handle when trying to get index.html
1. We are in a package environment so need to get the index.html that is packaged
allisonking marked this conversation as resolved.
Show resolved Hide resolved
2. We are in a development environment where index.html lives in src/
3. There is no index.html yet (clean repo)
"""

if not WEBAPP_INDEX.is_file():
WEBAPP_DIRECTORY.mkdir(parents=True, exist_ok=True)
with open(WEBAPP_DIRECTORY / "index.html", "w") as index_file:
index_file.write("<h1>Privacy is a Human Right!</h1>")
# TODO: test in the actual package environment!!
loader = pkgutil.get_loader("fidesctl") # the name of our package
if loader:
filename = loader.get_filename()
root_folder = Path(filename).parent.parent.parent
return root_folder / path
return None

app.mount("/static", StaticFiles(directory=WEBAPP_DIRECTORY), name="static")

def get_index_response() -> Response:
placeholder = "<h1>Privacy is a Human Right!</h1>"
index = get_ui_file(WEBAPP_INDEX)
return FileResponse(index) if index else Response(placeholder)


@app.on_event("startup")
Expand Down Expand Up @@ -111,19 +121,21 @@ def read_index() -> Response:
"""
Return an index.html at the root path
"""
return FileResponse(WEBAPP_INDEX)

return get_index_response()


@app.get("/{catchall:path}", response_class=FileResponse, tags=["Default"])
def read_other_paths(request: Request) -> FileResponse:
@app.get("/{catchall:path}", response_class=Response, tags=["Default"])
def read_other_paths(request: Request) -> Response:
"""
Return related frontend files. Adapted from https://github.com/tiangolo/fastapi/issues/130
"""
# check first if requested file exists (for frontend assets)
path = request.path_params["catchall"]
file = WEBAPP_DIRECTORY / Path(path)
if file.exists():
return FileResponse(file)
ui_file = get_ui_file(file)
if ui_file and ui_file.exists():
return FileResponse(ui_file)

# raise 404 for anything that should be backend endpoint but we can't find it
if path.startswith(API_PREFIX[1:]):
Expand All @@ -132,7 +144,7 @@ def read_other_paths(request: Request) -> FileResponse:
)

# otherwise return the index
return FileResponse(WEBAPP_INDEX)
return get_index_response()


def start_webserver() -> None:
Expand Down