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

Unified API Browser when using modular server #391

Merged
merged 1 commit into from
Mar 25, 2023
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
6 changes: 6 additions & 0 deletions .github/workflows/on_update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -57,6 +59,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand All @@ -80,6 +84,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down
16 changes: 15 additions & 1 deletion .github/workflows/pre_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -53,6 +55,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -83,6 +87,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand All @@ -99,7 +105,7 @@ jobs:
bandit -r src/
- name: Check dependencies for known security vulnerabilities with Safety
run: |
safety check
safety check -i 52495 -i 51457
test:
name: Test
Expand All @@ -116,6 +122,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -153,6 +161,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -188,6 +198,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -223,6 +235,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
steps:
- name: Checkout source at ${{ matrix.platform }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion bin/docker-compose-it.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DOCKER_COMPOSE_FILE_PATH=../${DOCKER_COMPOSE_FILE_NAME}
[ -f ${DOCKER_COMPOSE_FILE_PATH} ] || DOCKER_COMPOSE_FILE_PATH=${DOCKER_COMPOSE_FILE_NAME}

docker-compose -f ${DOCKER_COMPOSE_FILE_PATH} -p ci_it build --build-arg VERSION=$(date +%s)
docker-compose -f ${DOCKER_COMPOSE_FILE_PATH} -p ci_it up -d
docker-compose -f ${DOCKER_COMPOSE_FILE_PATH} -p ci_it --compatibility up -d

DOCKER_WAIT_FOR_SUT=$(docker wait ci_it_sut_1)
docker logs ci_it_sut_1
Expand Down
2 changes: 1 addition & 1 deletion bin/docker-compose-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DOCKER_COMPOSE_FILE_PATH=../${DOCKER_COMPOSE_FILE_NAME}
[ -f ${DOCKER_COMPOSE_FILE_PATH} ] || DOCKER_COMPOSE_FILE_PATH=${DOCKER_COMPOSE_FILE_NAME}

docker-compose -f ${DOCKER_COMPOSE_FILE_PATH} -p ci build --build-arg VERSION=$(date +%s)
docker-compose -f ${DOCKER_COMPOSE_FILE_PATH} -p ci up -d
docker-compose -f ${DOCKER_COMPOSE_FILE_PATH} -p ci --compatibility up -d

DOCKER_WAIT_FOR_PY36=$(docker wait ci_python3.6_1)
docker logs ci_python3.6_1
Expand Down
6 changes: 4 additions & 2 deletions docker-compose.it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
- SITE_PORT=5000
- WEB_URL=http://async_app:5000
- API_URL=http://async_app:5000/api
- BROWSABLE_API_URL=http://async_app:5000/browse
- BROWSABLE_API_URL=http://async_app:5000/api/browse
user: ${UID:-0}:${GID:-0}
depends_on:
- async_app
Expand All @@ -24,6 +24,7 @@ services:
- FLASK_ASYNC=1
environment:
- FLASK_ENV=TESTING
- FLASK_SERVER_NAME=async_app:5000
user: ${UID:-0}:${GID:-0}
command: >
python async_app.py
Expand All @@ -40,7 +41,7 @@ services:
- SITE_PORT=5000
- WEB_URL=http://app:5000
- API_URL=http://app:5000/api
- BROWSABLE_API_URL=http://app:5000/browse
- BROWSABLE_API_URL=http://app:5000/api/browse
user: ${UID:-0}:${GID:-0}
depends_on:
- app
Expand All @@ -51,6 +52,7 @@ services:
dockerfile: Dockerfile.local
environment:
- FLASK_ENV=TESTING
- FLASK_SERVER_NAME=app:5000
user: ${UID:-0}:${GID:-0}
command: >
python app.py
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
pylint src/ tests/ &&
mypy --install-types --non-interactive src/ &&
bandit -r src/ &&
safety check &&
safety check -i 51457 &&
pytest"

python3.9:
Expand Down
78 changes: 53 additions & 25 deletions src/flask_jsonrpc/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import typing as t
from urllib.parse import urlsplit

from flask import Flask

from .globals import default_jsonrpc_site, default_jsonrpc_site_api
from .helpers import urn
from .wrappers import JSONRPCDecoratorMixin
from .contrib.browse import create_browse
from .contrib.browse import JSONRPCBrowse

if t.TYPE_CHECKING:
from .site import JSONRPCSite
Expand All @@ -49,10 +50,11 @@ def __init__(
enable_web_browsable_api: bool = False,
) -> None:
self.app = app
self.service_url = service_url
self.path = service_url
self.base_url: t.Optional[str] = None
self.jsonrpc_site = jsonrpc_site()
self.jsonrpc_site_api = jsonrpc_site_api
self.browse_url = self._make_browse_url(service_url)
self.jsonrpc_browse: t.Optional[JSONRPCBrowse] = None
self.enable_web_browsable_api = enable_web_browsable_api
if app:
self.init_app(app)
Expand All @@ -63,44 +65,70 @@ def get_jsonrpc_site(self) -> 'JSONRPCSite':
def get_jsonrpc_site_api(self) -> t.Type['JSONRPCView']:
return self.jsonrpc_site_api

def _make_browse_url(self, service_url: str) -> str:
return ''.join([service_url, '/browse']) if not service_url.endswith('/') else ''.join([service_url, 'browse'])
def _make_jsonrpc_browse_url(self, path: str) -> str:
return ''.join([path.rstrip('/'), '/browse'])

def init_app(self, app: Flask) -> None:
http_host = app.config.get('SERVER_NAME')
app_root = app.config['APPLICATION_ROOT']
url_scheme = app.config['PREFERRED_URL_SCHEME']
url = urlsplit(self.path)

self.path = f"{app_root.rstrip('/')}{url.path}"
self.base_url = (
f"{url.scheme or url_scheme}://{url.netloc or http_host}/{self.path.lstrip('/')}" if http_host else None
)

self.get_jsonrpc_site().set_path(self.path)
self.get_jsonrpc_site().set_base_url(self.base_url)

app.add_url_rule(
self.service_url,
self.path,
view_func=self.get_jsonrpc_site_api().as_view(
urn('app', app.name, self.service_url), jsonrpc_site=self.get_jsonrpc_site()
urn('app', app.name, self.path), jsonrpc_site=self.get_jsonrpc_site()
),
)
self.register_browse(app, self)

if app.config['DEBUG'] or self.enable_web_browsable_api:
self.init_browse_app(app)

def register(self, view_func: t.Callable[..., t.Any], name: t.Optional[str] = None, **options: t.Any) -> None:
self.register_view_function(view_func, name, **options)

def register_blueprint(
self, app: Flask, jsonrpc_app: 'JSONRPCBlueprint', url_prefix: str, enable_web_browsable_api: bool = False
self,
app: Flask,
jsonrpc_app: 'JSONRPCBlueprint',
url_prefix: t.Optional[str] = None,
enable_web_browsable_api: bool = False,
) -> None:
service_url = ''.join([self.service_url, url_prefix]) if url_prefix else self.service_url
path = ''.join([self.path, '/', url_prefix.lstrip('/')]) if url_prefix else self.path
path_url = urlsplit(path)

url = urlsplit(self.base_url or path)
base_url = f"{url.scheme}://{url.netloc}/{url.path.lstrip('/')}" if self.base_url else None

jsonrpc_app.get_jsonrpc_site().set_path(path_url.path)
jsonrpc_app.get_jsonrpc_site().set_base_url(base_url)

app.add_url_rule(
service_url,
path,
view_func=jsonrpc_app.get_jsonrpc_site_api().as_view(
urn('blueprint', app.name, jsonrpc_app.name, service_url), jsonrpc_site=jsonrpc_app.get_jsonrpc_site()
urn('blueprint', app.name, jsonrpc_app.name, path), jsonrpc_site=jsonrpc_app.get_jsonrpc_site()
),
)

if enable_web_browsable_api:
self.register_browse(app, jsonrpc_app, url_prefix=url_prefix)
if app.config['DEBUG'] or enable_web_browsable_api:
self.register_browse(jsonrpc_app)

def register_browse(
self, app: Flask, jsonrpc_app: t.Union['JSONRPC', 'JSONRPCBlueprint'], url_prefix: t.Optional[str] = None
) -> None:
browse_url = ''.join([self.service_url, url_prefix, '/browse']) if url_prefix else self.browse_url
if app.config['DEBUG'] or self.enable_web_browsable_api:
app.register_blueprint(
create_browse(urn('browse', app.name, browse_url), jsonrpc_app.get_jsonrpc_site()),
url_prefix=browse_url,
)
app.add_url_rule(
browse_url + '/static/<path:filename>', 'urn:browse.static', view_func=app.send_static_file
def init_browse_app(self, app: Flask, path: t.Optional[str] = None, base_url: t.Optional[str] = None) -> None:
browse_url = self._make_jsonrpc_browse_url(path or self.path)
self.jsonrpc_browse = JSONRPCBrowse(app, url_prefix=browse_url, base_url=base_url or self.base_url)
self.jsonrpc_browse.register_jsonrpc_site(self.get_jsonrpc_site())

def register_browse(self, jsonrpc_app: t.Union['JSONRPC', 'JSONRPCBlueprint']) -> None:
if not self.jsonrpc_browse:
raise RuntimeError(
'You need to init the Browse app before register the Site, see JSONRPC.init_browse_app(...)'
)
self.jsonrpc_browse.register_jsonrpc_site(jsonrpc_app.get_jsonrpc_site())
Loading