From aa5358dbda49a152d130262ac5a562ced36dd482 Mon Sep 17 00:00:00 2001 From: Matt Mackay Date: Wed, 25 May 2022 17:22:12 -0400 Subject: [PATCH] chore: tidy and add ipython example --- .gitignore | 2 +- BUILD.bazel | 8 ++ examples/django/BUILD.bazel | 10 ++ examples/django/README.md | 24 ++++ examples/django/manage.py | 22 ++++ examples/django/mysite/BUILD.bazel | 16 +++ examples/django/mysite/__init__.py | 0 examples/django/mysite/asgi.py | 16 +++ examples/django/mysite/settings.py | 123 ++++++++++++++++++ examples/django/mysite/urls.py | 21 +++ examples/django/mysite/wsgi.py | 16 +++ examples/foo/BUILD.bazel | 8 -- examples/foo/__init__.py | 2 - internal_python_deps.bzl | 4 +- py/private/entry.tmpl.sh | 2 +- py/tests/external-deps/BUILD.bazel | 8 -- py/tests/external-deps/__main__.py | 4 - py/tests/external-deps/expected | 4 - .../requirements.in => requirements.in | 0 .../requirements.txt => requirements.txt | 6 +- 20 files changed, 263 insertions(+), 33 deletions(-) create mode 100644 examples/django/BUILD.bazel create mode 100644 examples/django/README.md create mode 100644 examples/django/manage.py create mode 100644 examples/django/mysite/BUILD.bazel create mode 100644 examples/django/mysite/__init__.py create mode 100644 examples/django/mysite/asgi.py create mode 100644 examples/django/mysite/settings.py create mode 100644 examples/django/mysite/urls.py create mode 100644 examples/django/mysite/wsgi.py delete mode 100644 examples/foo/BUILD.bazel delete mode 100644 examples/foo/__init__.py rename py/tests/external-deps/requirements.in => requirements.in (100%) rename py/tests/external-deps/requirements.txt => requirements.txt (83%) diff --git a/.gitignore b/.gitignore index fa2c4ec9..967e5733 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -bazel-* +**/bazel-* .bazelrc.user .idea .ijwb diff --git a/BUILD.bazel b/BUILD.bazel index f0453a15..cfda797e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_python//python/pip_install:requirements.bzl", "compile_pip_requirements") load("@bazel_gazelle//:def.bzl", "gazelle", "gazelle_binary") # gazelle:exclude internal_python_deps.bzl @@ -12,3 +13,10 @@ gazelle( name = "gazelle", gazelle = "gazelle_bin", ) + +compile_pip_requirements( + name = "requirements", + extra_args = ["--allow-unsafe"], + requirements_in = "requirements.in", + requirements_txt = "requirements.txt", +) diff --git a/examples/django/BUILD.bazel b/examples/django/BUILD.bazel new file mode 100644 index 00000000..508220b9 --- /dev/null +++ b/examples/django/BUILD.bazel @@ -0,0 +1,10 @@ +load("@aspect_rules_py//py:defs.bzl", "py_binary") + +py_binary( + name = "django", + srcs = ["manage.py"], + deps = [ + "//examples/django/mysite", + "@pypi_django//:wheel", + ], +) diff --git a/examples/django/README.md b/examples/django/README.md new file mode 100644 index 00000000..78d05290 --- /dev/null +++ b/examples/django/README.md @@ -0,0 +1,24 @@ +# Django application with rules_py + +This is a minimal example of using Django with rules_py. It relies on the hermetic Python 3.9 interpreter +from rules_python and uses `pip_parse` to fetch PyPi dependencies. + +This demo shows the interaction of external wheels, as well as the ability to create a simple Python virtual environment +for running locally from the command line or from with an IDE. + +To start the interactive shell, at the root of this workspace: + +```bash +$ bazel run django +``` + +A Python virtual environment can be created that is suitable for IDE consumption. IDEs such as VSCode and PyCharm can be +configured to use this local venv, therefore using the bazel managed interpreter, pip and fetched PyPi packages. + +To create the venv, run the following: + +```bash +$ bazel run django.venv +``` + +This will create a venv at the root of the workspace ready for IDE configuration. diff --git a/examples/django/manage.py b/examples/django/manage.py new file mode 100644 index 00000000..a7da6671 --- /dev/null +++ b/examples/django/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/examples/django/mysite/BUILD.bazel b/examples/django/mysite/BUILD.bazel new file mode 100644 index 00000000..31191e16 --- /dev/null +++ b/examples/django/mysite/BUILD.bazel @@ -0,0 +1,16 @@ +load("@aspect_rules_py//py:defs.bzl", "py_library") + +py_library( + name = "mysite", + srcs = [ + "__init__.py", + "asgi.py", + "settings.py", + "urls.py", + "wsgi.py", + ], + visibility = ["//examples/django:__pkg__"], + deps = [ + "@pypi_django//:wheel", + ], +) diff --git a/examples/django/mysite/__init__.py b/examples/django/mysite/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/django/mysite/asgi.py b/examples/django/mysite/asgi.py new file mode 100644 index 00000000..674a5a5a --- /dev/null +++ b/examples/django/mysite/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for mysite project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') + +application = get_asgi_application() diff --git a/examples/django/mysite/settings.py b/examples/django/mysite/settings.py new file mode 100644 index 00000000..ac5bff4d --- /dev/null +++ b/examples/django/mysite/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for mysite project. + +Generated by 'django-admin startproject' using Django 4.0.4. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-7v4$nj3jlwk*ysudb-%mgr^4g+zzdr6q*&2iy=8+4(ixp3*2hz' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'mysite.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'mysite.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/examples/django/mysite/urls.py b/examples/django/mysite/urls.py new file mode 100644 index 00000000..f46d600c --- /dev/null +++ b/examples/django/mysite/urls.py @@ -0,0 +1,21 @@ +"""mysite URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/examples/django/mysite/wsgi.py b/examples/django/mysite/wsgi.py new file mode 100644 index 00000000..22f8ea99 --- /dev/null +++ b/examples/django/mysite/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for rules_py project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') + +application = get_wsgi_application() diff --git a/examples/foo/BUILD.bazel b/examples/foo/BUILD.bazel deleted file mode 100644 index 130a2d3f..00000000 --- a/examples/foo/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -load("//py:defs.bzl", "py_library") - -py_library( - name = "foo", - srcs = ["__init__.py"], - imports = [".."], - visibility = ["//:__subpackages__"], -) diff --git a/examples/foo/__init__.py b/examples/foo/__init__.py deleted file mode 100644 index 39fe20c8..00000000 --- a/examples/foo/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -def get_branding(): - return "rules_py" diff --git a/internal_python_deps.bzl b/internal_python_deps.bzl index 2a52338e..d84440d9 100644 --- a/internal_python_deps.bzl +++ b/internal_python_deps.bzl @@ -21,7 +21,7 @@ def rules_py_internal_pypi_deps(interpreter): # package. For details on `package_annotation` and it's uses, see the # docs at @rules_python//docs:pip.md`. - PACKAGES = ["django", "colorama"] + PACKAGES = ["django", "colorama", "django"] ANNOTATIONS = { pkg: package_annotation(additive_build_content = PY_WHEEL_RULE_CONTENT) for pkg in PACKAGES @@ -31,5 +31,5 @@ def rules_py_internal_pypi_deps(interpreter): name = "pypi", annotations = ANNOTATIONS, python_interpreter_target = interpreter, - requirements_lock = "//py/tests/external-deps:requirements.txt", + requirements_lock = "//:requirements.txt", ) diff --git a/py/private/entry.tmpl.sh b/py/private/entry.tmpl.sh index 9417af3a..38f1a570 100644 --- a/py/private/entry.tmpl.sh +++ b/py/private/entry.tmpl.sh @@ -106,7 +106,7 @@ activate_venv "${VENV_LOCATION}" RUN_BINARY_ENTRY_POINT={{RUN_BINARY_ENTRY_POINT}} if [ "$RUN_BINARY_ENTRY_POINT" = true ]; then # Finally, launch the entrypoint - ${VPYTHON} "${ENTRYPOINT}" -- "$@" + ${VPYTHON} "${ENTRYPOINT}" "$@" fi deactivate_venv diff --git a/py/tests/external-deps/BUILD.bazel b/py/tests/external-deps/BUILD.bazel index be49f77c..cf50f040 100644 --- a/py/tests/external-deps/BUILD.bazel +++ b/py/tests/external-deps/BUILD.bazel @@ -1,14 +1,7 @@ -load("@rules_python//python/pip_install:requirements.bzl", "compile_pip_requirements") load("//py:defs.bzl", "py_binary", "py_library") load("@python_toolchain//:defs.bzl", "host_platform") load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files") -compile_pip_requirements( - name = "requirements", - requirements_in = "requirements.in", - requirements_txt = "requirements.txt", -) - py_library( name = "lib", srcs = ["lib.py"], @@ -22,7 +15,6 @@ py_binary( srcs = ["__main__.py"], deps = [ ":lib", - "//examples/foo", "@pypi_django//:wheel", ], ) diff --git a/py/tests/external-deps/__main__.py b/py/tests/external-deps/__main__.py index 2ed97429..f7f395f7 100644 --- a/py/tests/external-deps/__main__.py +++ b/py/tests/external-deps/__main__.py @@ -22,7 +22,3 @@ from lib import greet print(f'\nFrom lib with wheel dependency: {greet("Matt")}') print(f'lib filepath: {inspect.getsourcefile(greet)}') - -from foo import get_branding -print(f"From lib in another package: {get_branding()}") -print(f'lib filepath: {inspect.getsourcefile(get_branding)}') diff --git a/py/tests/external-deps/expected b/py/tests/external-deps/expected index ec3b30f9..5e1ce936 100644 --- a/py/tests/external-deps/expected +++ b/py/tests/external-deps/expected @@ -11,8 +11,6 @@ sys path: (py_toolchain)/lib/python3.9/lib-dynload (pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/main.venv/lib/python3.9/site-packages (pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/aspect_rules_py/py/tests/external-deps -(pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/aspect_rules_py/examples -(pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/aspect_rules_py/examples/foo Entrypoint Path: (pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/aspect_rules_py/py/tests/external-deps/__main__.py @@ -21,5 +19,3 @@ Django version: 4.0.2 From lib with wheel dependency: Hello Matt lib filepath: (pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/aspect_rules_py/py/tests/external-deps/lib.py -From lib in another package: rules_py -lib filepath: (pwd)/bazel-out/host/bin/py/tests/external-deps/main.runfiles/aspect_rules_py/examples/foo/__init__.py diff --git a/py/tests/external-deps/requirements.in b/requirements.in similarity index 100% rename from py/tests/external-deps/requirements.in rename to requirements.in diff --git a/py/tests/external-deps/requirements.txt b/requirements.txt similarity index 83% rename from py/tests/external-deps/requirements.txt rename to requirements.txt index 6c7e7422..4959953a 100644 --- a/py/tests/external-deps/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile # To update, run: # -# bazel run //py/tests/external-deps:requirements.update +# bazel run //:requirements.update # asgiref==3.5.0 \ --hash=sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0 \ @@ -11,11 +11,11 @@ asgiref==3.5.0 \ colorama==0.4.4 \ --hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b \ --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 - # via -r py/tests/external-deps/requirements.in + # via -r requirements.in django==4.0.2 \ --hash=sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a \ --hash=sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a - # via -r py/tests/external-deps/requirements.in + # via -r requirements.in sqlparse==0.4.2 \ --hash=sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae \ --hash=sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d