├── BUILDROOT ├── helloworld ├── __init__.py ├── greet │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── BUILD │ │ ├── 0001_initial.py │ │ └── 0002_data.py │ ├── conftest.py │ ├── admin.py │ ├── urls.py │ ├── BUILD │ ├── models_test.py │ ├── models.py │ └── views.py ├── ui │ ├── __init__.py │ ├── static │ │ ├── helloworld │ │ │ └── ui │ │ │ │ └── helloworld.css │ │ └── BUILD │ ├── templates │ │ ├── BUILD │ │ └── helloworld │ │ │ └── ui │ │ │ └── index.html │ ├── BUILD │ ├── urls.py │ └── views.py ├── util │ ├── __init__.py │ ├── BUILD │ ├── mode.py │ ├── backend.py │ ├── discovery.py │ ├── settings_for_tests.py │ ├── per_app_db_router.py │ └── service.py ├── person │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── BUILD │ │ ├── 0002_data.py │ │ └── 0001_initial.py │ ├── conftest.py │ ├── admin.py │ ├── urls.py │ ├── models_test.py │ ├── BUILD │ ├── models.py │ └── views.py ├── service │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── gunicorn.py │ │ ├── manage.py │ │ ├── urls.py │ │ ├── BUILD │ │ └── settings.py │ ├── user │ │ ├── __init__.py │ │ ├── manage.py │ │ ├── gunicorn.py │ │ ├── urls.py │ │ ├── settings.py │ │ └── BUILD │ ├── welcome │ │ ├── __init__.py │ │ ├── manage.py │ │ ├── gunicorn.py │ │ ├── urls.py │ │ ├── settings.py │ │ └── BUILD │ └── frontend │ │ ├── __init__.py │ │ ├── manage.py │ │ ├── gunicorn.py │ │ ├── urls.py │ │ ├── settings.py │ │ └── BUILD ├── translate │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── BUILD │ │ ├── 0001_initial.py │ │ └── 0002_data.py │ ├── admin.py │ ├── conftest.py │ ├── urls.py │ ├── BUILD │ ├── views.py │ ├── models_test.py │ └── models.py ├── BUILD ├── gunicorn_conf.py ├── wsgi.py └── settings_base.py ├── BUILD ├── pytest.ini ├── pants.ci.toml ├── .flake8 ├── .isort.cfg ├── requirements.txt ├── .gitignore ├── mypy.ini ├── pants.toml ├── .github └── workflows │ └── pants.yaml ├── README.md ├── LICENSE └── lockfiles └── python-default.lock /BUILDROOT: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/greet/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/person/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/service/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/translate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/service/admin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/service/user/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/service/welcome/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/greet/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/person/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/service/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/translate/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helloworld/greet/migrations/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /helloworld/person/migrations/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /helloworld/translate/migrations/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /helloworld/ui/static/helloworld/ui/helloworld.css: -------------------------------------------------------------------------------- 1 | 2 | .greeting { 3 | color: blue; 4 | } 5 | -------------------------------------------------------------------------------- /helloworld/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources() 5 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_requirements(name="requirements") 5 | -------------------------------------------------------------------------------- /helloworld/ui/static/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | resources(name="static", sources=["**/*"]) 5 | -------------------------------------------------------------------------------- /helloworld/ui/templates/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | resources(name="templates", sources=["**/*"]) 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | [pytest] 5 | django_find_project = false 6 | addopts = --reuse-db 7 | -------------------------------------------------------------------------------- /helloworld/util/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources(dependencies=["helloworld/gunicorn_conf.py"]) 5 | -------------------------------------------------------------------------------- /helloworld/gunicorn_conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | import multiprocessing 5 | 6 | workers = multiprocessing.cpu_count() * 2 + 1 7 | -------------------------------------------------------------------------------- /pants.ci.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # See https://www.pantsbuild.org/docs/using-pants-in-ci. 5 | 6 | [GLOBAL] 7 | dynamic_ui = false 8 | colors = true 9 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # E203 -> whitespace before ':' (conflicts with Black) 3 | # E231 -> Bad trailing comma (conflicts with Black) 4 | # E501 -> line too long (conflicts with Black) 5 | 6 | extend-ignore: 7 | E203, 8 | E231, 9 | E501, 10 | -------------------------------------------------------------------------------- /helloworld/greet/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.settings_for_tests import configure_settings 5 | 6 | configure_settings(["helloworld.greet"]) 7 | -------------------------------------------------------------------------------- /helloworld/person/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.settings_for_tests import configure_settings 5 | 6 | configure_settings(["helloworld.person"]) 7 | -------------------------------------------------------------------------------- /helloworld/greet/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.contrib import admin 5 | 6 | from helloworld.greet.models import Greeting 7 | 8 | admin.site.register(Greeting) 9 | -------------------------------------------------------------------------------- /helloworld/person/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.contrib import admin 5 | 6 | from helloworld.person.models import Person 7 | 8 | admin.site.register(Person) 9 | -------------------------------------------------------------------------------- /helloworld/service/user/manage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_manage() 8 | -------------------------------------------------------------------------------- /helloworld/service/admin/gunicorn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_gunicorn() 8 | -------------------------------------------------------------------------------- /helloworld/service/admin/manage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_manage() 8 | -------------------------------------------------------------------------------- /helloworld/service/frontend/manage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_manage() 8 | -------------------------------------------------------------------------------- /helloworld/service/user/gunicorn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_gunicorn() 8 | -------------------------------------------------------------------------------- /helloworld/service/welcome/manage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_manage() 8 | -------------------------------------------------------------------------------- /helloworld/service/frontend/gunicorn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_gunicorn() 8 | -------------------------------------------------------------------------------- /helloworld/service/user/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import include, path 5 | 6 | urlpatterns = [ 7 | path("person/", include("helloworld.person.urls")), 8 | ] 9 | -------------------------------------------------------------------------------- /helloworld/service/welcome/gunicorn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.service import Service 5 | 6 | if __name__ == "__main__": 7 | Service(__file__).run_gunicorn() 8 | -------------------------------------------------------------------------------- /helloworld/ui/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | dependencies=[ 6 | "helloworld/ui/static", 7 | "helloworld/ui/templates", 8 | ] 9 | ) 10 | -------------------------------------------------------------------------------- /helloworld/translate/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.contrib import admin 5 | 6 | from helloworld.translate.models import Translation 7 | 8 | admin.site.register(Translation) 9 | -------------------------------------------------------------------------------- /helloworld/translate/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.settings_for_tests import configure_settings 5 | 6 | configure_settings(["helloworld.greet", "helloworld.translate"]) 7 | -------------------------------------------------------------------------------- /helloworld/service/admin/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.contrib import admin 5 | from django.urls import path 6 | 7 | urlpatterns = [ 8 | path("admin/", admin.site.urls), 9 | ] 10 | -------------------------------------------------------------------------------- /helloworld/ui/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import path 5 | 6 | from helloworld.ui import views 7 | 8 | urlpatterns = [ 9 | path("", views.index, name="index"), 10 | ] 11 | -------------------------------------------------------------------------------- /helloworld/person/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import path 5 | 6 | from helloworld.person import views 7 | 8 | urlpatterns = [ 9 | path("/", views.index, name="index"), 10 | ] 11 | -------------------------------------------------------------------------------- /helloworld/ui/templates/helloworld/ui/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | {{ greeting }}, {{ name }}. 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /helloworld/translate/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import path 5 | 6 | from helloworld.translate import views 7 | 8 | urlpatterns = [ 9 | path("//", views.index, name="index"), 10 | ] 11 | -------------------------------------------------------------------------------- /helloworld/service/frontend/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import include, path 5 | 6 | urlpatterns = [ 7 | path("", include("helloworld.ui.urls")), 8 | path("__debug__/", include("debug_toolbar.urls")), 9 | ] 10 | -------------------------------------------------------------------------------- /helloworld/service/welcome/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import include, path 5 | 6 | urlpatterns = [ 7 | path("greet/", include("helloworld.greet.urls")), 8 | path("translate/", include("helloworld.translate.urls")), 9 | ] 10 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | # This is to make isort compatible with Black. See 3 | # https://black.readthedocs.io/en/stable/the_black_code_style.html#how-black-wraps-lines. 4 | line_length=88 5 | multi_line_output=3 6 | include_trailing_comma=True 7 | force_grid_wrap=0 8 | use_parentheses=True 9 | 10 | known_first_party=helloworld 11 | default_section=THIRDPARTY 12 | -------------------------------------------------------------------------------- /helloworld/wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | import os 5 | 6 | from django.core.wsgi import get_wsgi_application 7 | 8 | if "DJANGO_SETTINGS_MODULE" not in os.environ: 9 | raise ValueError("DJANGO_SETTINGS_MODULE must be set before creating a WSGI app.") 10 | 11 | application = get_wsgi_application() 12 | -------------------------------------------------------------------------------- /helloworld/person/models_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | import pytest 5 | 6 | from helloworld.person.models import Person 7 | 8 | 9 | @pytest.mark.django_db 10 | def test_database_is_seeded(): 11 | sherlock = Person.objects.get(slug="sherlock") 12 | assert "Sherlock Holmes" == sherlock.full_name 13 | -------------------------------------------------------------------------------- /helloworld/util/mode.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from enum import Enum 5 | 6 | from django.conf import settings 7 | 8 | 9 | class Mode(Enum): 10 | DEV = "DEV" 11 | STAGING = "STAGING" 12 | PROD = "PROD" 13 | 14 | 15 | def get_mode() -> Mode: 16 | return Mode[getattr(settings, "HELLOWORLD_MODE", "DEV")] 17 | -------------------------------------------------------------------------------- /helloworld/service/user/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.settings_base import * # noqa: F401, F403 5 | 6 | ROOT_URLCONF = "helloworld.service.user.urls" 7 | 8 | INSTALLED_APPS = [ 9 | "django.contrib.contenttypes", 10 | "helloworld.person", 11 | ] 12 | 13 | set_up_database("users") # noqa: F405 14 | -------------------------------------------------------------------------------- /helloworld/greet/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.urls import path, re_path 5 | 6 | from helloworld.greet import views 7 | 8 | urlpatterns = [ 9 | re_path( 10 | r"^tod/(?P\d\d:\d\d:\d\d)/$", views.for_time_of_day, name="tod" 11 | ), 12 | path("/", views.index, name="index"), 13 | ] 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | ansicolors>=1.0.2 5 | django>=3.2.13,<4 6 | django-debug-toolbar>=3.8.0,<4 7 | django-stubs~=4.2.7 8 | gunicorn>=20.1.0 9 | mypy==1.18.1 10 | setuptools>=42.0.0 11 | translate>=3.2.1 12 | protobuf>=3.11.3 13 | pytest>=6.0.1 14 | pytest-django>=4,<5 15 | requests>=2.25.1 16 | types-requests>=2.25.1 17 | -------------------------------------------------------------------------------- /helloworld/greet/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | dependencies=[ 6 | "helloworld/greet/migrations", 7 | ] 8 | ) 9 | 10 | python_tests( 11 | name="tests", 12 | dependencies=[ 13 | "helloworld", # For settings.py. 14 | ], 15 | ) 16 | 17 | python_test_utils( 18 | name="test_utils", 19 | ) 20 | -------------------------------------------------------------------------------- /helloworld/person/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | dependencies=[ 6 | "helloworld/person/migrations", 7 | ] 8 | ) 9 | 10 | python_tests( 11 | name="tests", 12 | dependencies=[ 13 | "helloworld", # For settings.py. 14 | ], 15 | ) 16 | 17 | python_test_utils( 18 | name="test_utils", 19 | ) 20 | -------------------------------------------------------------------------------- /helloworld/translate/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | dependencies=[ 6 | "helloworld/translate/migrations", 7 | ] 8 | ) 9 | 10 | python_tests( 11 | name="tests", 12 | dependencies=[ 13 | "helloworld", # For settings.py. 14 | ], 15 | ) 16 | 17 | python_test_utils( 18 | name="test_utils", 19 | ) 20 | -------------------------------------------------------------------------------- /helloworld/service/welcome/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.settings_base import * # noqa: F401, F403 5 | 6 | ROOT_URLCONF = "helloworld.service.welcome.urls" 7 | 8 | INSTALLED_APPS = [ 9 | "django.contrib.contenttypes", 10 | "helloworld.greet", 11 | "helloworld.translate", 12 | ] 13 | 14 | set_up_database("greetings") # noqa: F405 15 | -------------------------------------------------------------------------------- /helloworld/person/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.db import models 5 | 6 | 7 | # Note: This is distinct from the standard auth app's User model, for clarity. 8 | class Person(models.Model): 9 | slug = models.CharField(max_length=20, unique=True) 10 | full_name = models.CharField(max_length=50) 11 | 12 | def __str__(self): 13 | return self.slug 14 | -------------------------------------------------------------------------------- /helloworld/util/backend.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.util.discovery import get_dev_port 5 | from helloworld.util.mode import Mode, get_mode 6 | 7 | 8 | def get_backend_url(name: str) -> str: 9 | if get_mode() == Mode.DEV: 10 | return f"http://localhost:{get_dev_port(name)}" 11 | else: 12 | raise ValueError(f"Mode {get_mode()} not supported yet.") 13 | -------------------------------------------------------------------------------- /helloworld/person/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.http import Http404, HttpResponse 5 | 6 | from helloworld.person.models import Person 7 | 8 | 9 | def index(request, slug): 10 | try: 11 | person_to_greet = Person.objects.get(slug=slug) 12 | return HttpResponse(person_to_greet.full_name) 13 | except Person.DoesNotExist: 14 | raise Http404(f"No such person: {slug}") 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # Pants workspace files 5 | /.pants.d/ 6 | /dist 7 | /.pids 8 | /.pants.workdir.file_lock* 9 | 10 | Local venv for running manage.py etc. 11 | /.venvs 12 | 13 | # Python files 14 | __pycache__/ 15 | *.pyc 16 | .venv/ 17 | 18 | # Editors 19 | .idea/ 20 | *.iml 21 | 22 | # Local databases. 23 | *.sqlite3 24 | *.sqlite3-journal 25 | 26 | # MacOS attribute files 27 | .DS_Store 28 | 29 | -------------------------------------------------------------------------------- /helloworld/util/discovery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | 5 | DEV_PORTS = { 6 | "helloworld.service.frontend": 8000, 7 | "helloworld.service.admin": 8001, 8 | "helloworld.service.user": 8010, 9 | "helloworld.service.welcome": 8020, 10 | } 11 | 12 | 13 | def get_dev_port(service: str) -> int: 14 | try: 15 | return DEV_PORTS[service] 16 | except KeyError: 17 | raise ValueError(f"No dev port found for service {service}") 18 | -------------------------------------------------------------------------------- /helloworld/translate/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.http import Http404, HttpResponse 5 | 6 | from helloworld.translate.models import Translation 7 | 8 | 9 | def index(request, slug: str, lang: str) -> HttpResponse: 10 | try: 11 | translation = Translation.objects.get(greeting__slug=slug, lang=lang) 12 | return HttpResponse(translation.translation) 13 | except Translation.DoesNotExist: 14 | raise Http404(f"No translation in {lang} for {slug}") 15 | -------------------------------------------------------------------------------- /helloworld/service/frontend/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.settings_base import * # noqa: F401, F403 5 | 6 | ROOT_URLCONF = "helloworld.service.frontend.urls" 7 | 8 | INSTALLED_APPS = [ 9 | "django.contrib.contenttypes", 10 | "django.contrib.auth", 11 | "django.contrib.staticfiles", 12 | "debug_toolbar", 13 | "helloworld.ui", 14 | ] 15 | 16 | MIDDLEWARE += [ # noqa: F405 17 | "debug_toolbar.middleware.DebugToolbarMiddleware", 18 | ] 19 | 20 | INTERNAL_IPS = [ 21 | "127.0.0.1", 22 | ] 23 | 24 | STATIC_URL = "static/" 25 | STATIC_ROOT = "/tmp/static_root" 26 | -------------------------------------------------------------------------------- /helloworld/translate/models_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | import pytest 5 | 6 | from helloworld.greet.models import Greeting 7 | from helloworld.translate.models import ( # noqa: F401 (so fk related name works). 8 | Translation, 9 | ) 10 | 11 | 12 | @pytest.mark.django_db 13 | def test_database_is_seeded(): 14 | hello = Greeting.objects.get(slug="hello") 15 | translations = {tr.lang: tr.translation for tr in hello.translation_set.all()} 16 | assert { 17 | "en": "Hello", 18 | "es": "Hola", 19 | "fr": "Allo", 20 | "de": "Hallo", 21 | } == translations 22 | -------------------------------------------------------------------------------- /helloworld/service/user/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | name="lib", 6 | dependencies=[ 7 | "helloworld/person", 8 | ], 9 | overrides={ 10 | "manage.py": { 11 | "dependencies": ["helloworld/service/user/settings.py:lib"], 12 | "restartable": True, 13 | }, 14 | "gunicorn.py": {"restartable": True}, 15 | }, 16 | ) 17 | 18 | pex_binary( 19 | name="gunicorn", 20 | entry_point="gunicorn.py", 21 | dependencies=[ 22 | ":lib", 23 | ], 24 | ) 25 | 26 | pex_binary( 27 | name="manage", 28 | entry_point="manage.py", 29 | dependencies=[ 30 | ":lib", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /helloworld/service/frontend/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | name="lib", 6 | dependencies=[ 7 | "helloworld/ui", 8 | ], 9 | overrides={ 10 | "manage.py": { 11 | "dependencies": ["helloworld/service/frontend/settings.py:lib"], 12 | "restartable": True, 13 | }, 14 | "gunicorn.py": {"restartable": True}, 15 | }, 16 | ) 17 | 18 | pex_binary( 19 | name="gunicorn", 20 | entry_point="gunicorn.py", 21 | dependencies=[ 22 | ":lib", 23 | ], 24 | ) 25 | 26 | pex_binary( 27 | name="manage", 28 | entry_point="manage.py", 29 | dependencies=[ 30 | ":lib", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /helloworld/service/welcome/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | name="lib", 6 | dependencies=[ 7 | "helloworld/greet", 8 | "helloworld/translate", 9 | ], 10 | overrides={ 11 | "manage.py": { 12 | "dependencies": ["helloworld/service/welcome/settings.py:lib"], 13 | "restartable": True, 14 | }, 15 | "gunicorn.py": {"restartable": True}, 16 | }, 17 | ) 18 | 19 | pex_binary( 20 | name="gunicorn", 21 | entry_point="gunicorn.py", 22 | dependencies=[ 23 | ":lib", 24 | ], 25 | ) 26 | 27 | pex_binary( 28 | name="manage", 29 | entry_point="manage.py", 30 | dependencies=[ 31 | ":lib", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /helloworld/service/admin/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources( 5 | name="lib", 6 | dependencies=[ 7 | "helloworld/greet", 8 | "helloworld/person", 9 | "helloworld/translate", 10 | ], 11 | overrides={ 12 | "manage.py": { 13 | "dependencies": ["helloworld/service/admin/settings.py:lib"], 14 | "restartable": True, 15 | }, 16 | "gunicorn.py": {"restartable": True}, 17 | }, 18 | ) 19 | 20 | pex_binary( 21 | name="gunicorn", 22 | entry_point="gunicorn.py", 23 | dependencies=[ 24 | ":lib", 25 | ], 26 | ) 27 | 28 | pex_binary( 29 | name="manage", 30 | entry_point="manage.py", 31 | dependencies=[ 32 | ":lib", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /helloworld/util/settings_for_tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import annotations 5 | 6 | import os 7 | from tempfile import mkdtemp 8 | 9 | from django.conf import settings 10 | 11 | 12 | def configure_settings(apps: list[str]) -> None: 13 | """Minimal settings for unittests.""" 14 | settings.configure( 15 | TIME_ZONE="UTC", 16 | USE_TZ=True, 17 | INSTALLED_APPS=apps, 18 | DATABASES={ 19 | "default": { 20 | "ENGINE": "django.db.backends.sqlite3", 21 | "NAME": os.path.join( 22 | mkdtemp(), 23 | f"test{os.environ.get('PANTS_EXECUTION_SLOT', '')}.sqlite3", 24 | ), 25 | } 26 | }, 27 | ) 28 | -------------------------------------------------------------------------------- /helloworld/greet/models_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from datetime import time 5 | 6 | import pytest 7 | 8 | from helloworld.greet.models import Greeting 9 | 10 | 11 | @pytest.mark.django_db 12 | def test_database_is_seeded(): 13 | hello = Greeting.objects.get(slug="hello") 14 | assert "Hello" == hello.salutation 15 | 16 | 17 | @pytest.mark.django_db 18 | def test_for_time_of_day(): 19 | assert ( 20 | "goodmorning" == Greeting.for_time_of_day(time.fromisoformat("09:30:57")).slug 21 | ) 22 | assert ( 23 | "goodevening" == Greeting.for_time_of_day(time.fromisoformat("18:03:09")).slug 24 | ) 25 | assert "goodnight" == Greeting.for_time_of_day(time.fromisoformat("23:47:45")).slug 26 | assert Greeting.for_time_of_day(time.fromisoformat("03:03:03")) is None 27 | -------------------------------------------------------------------------------- /helloworld/greet/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import annotations 5 | 6 | from datetime import time 7 | 8 | from django.db import models 9 | 10 | 11 | class Greeting(models.Model): 12 | slug = models.CharField(max_length=30, unique=True) 13 | start_time = models.TimeField(null=True) 14 | end_time = models.TimeField(null=True) 15 | salutation = models.CharField(max_length=30) 16 | 17 | @classmethod 18 | def for_time_of_day(cls, time_of_day: time) -> "Greeting" | None: 19 | greetings: list["Greeting"] = list( 20 | cls.objects.filter(start_time__lte=time_of_day, end_time__gte=time_of_day) 21 | ) 22 | if greetings: 23 | return greetings[0] 24 | return None 25 | 26 | def __str__(self): 27 | return self.slug 28 | -------------------------------------------------------------------------------- /helloworld/greet/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from datetime import time 5 | 6 | from django.http import Http404, HttpResponse 7 | 8 | from helloworld.greet.models import Greeting 9 | 10 | 11 | def index(request, slug): 12 | try: 13 | greeting = Greeting.objects.get(slug=slug) 14 | return HttpResponse(greeting.salutation) 15 | except Greeting.DoesNotExist: 16 | raise Http404(f"No such greeting: {slug}") 17 | 18 | 19 | def for_time_of_day(request, time_of_day: str): 20 | greeting = Greeting.for_time_of_day(time.fromisoformat(time_of_day)) 21 | if not greeting: 22 | # Fall back to a generic greeting. 23 | greeting = Greeting.objects.get(slug="hello") 24 | if greeting is None: 25 | raise Http404(f"No greeting found for time of day: {time_of_day}") 26 | return HttpResponse(greeting.slug) 27 | -------------------------------------------------------------------------------- /helloworld/person/migrations/0002_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # Generated by Django 3.0 on 2020-06-17 22:09 5 | 6 | from django.db import migrations 7 | 8 | 9 | def create_people_to_greet(apps, schema_editor): 10 | Person = apps.get_model("person", "Person") 11 | 12 | def create(slug, full_name): 13 | Person(slug=slug, full_name=full_name).save() 14 | 15 | create("sherlock", "Sherlock Holmes") 16 | create("watson", "John Watson") 17 | create("lestrade", "Inspector G. Lestrade") 18 | create("hudson", "Mrs. Hudson") 19 | create("adler", "Irene Adler") 20 | create("moriarty", "James Moriarty") 21 | 22 | 23 | class Migration(migrations.Migration): 24 | dependencies = [ 25 | ("person", "0001_initial"), 26 | ] 27 | 28 | operations = [ 29 | migrations.RunPython(create_people_to_greet), 30 | ] 31 | -------------------------------------------------------------------------------- /helloworld/person/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # Generated by Django 3.2.3 on 2021-05-15 16:12 5 | 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | initial = True 11 | 12 | dependencies = [] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Person", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ("slug", models.CharField(max_length=20, unique=True)), 28 | ("full_name", models.CharField(max_length=50)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /helloworld/translate/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.db import models 5 | 6 | from helloworld.greet.models import Greeting 7 | 8 | 9 | class TranslationManager(models.Manager): 10 | def get_queryset(self): 11 | qs = super().get_queryset() 12 | return qs.select_related("greeting") 13 | 14 | 15 | class Translation(models.Model): 16 | class Meta: 17 | constraints = [ 18 | models.UniqueConstraint(fields=["greeting", "lang"], name="greeting_lang") 19 | ] 20 | 21 | objects = TranslationManager() 22 | 23 | # NB: This Translation model share a database with the Greeting model, so this 24 | # relation is allowed by our database router. 25 | greeting = models.ForeignKey(Greeting, on_delete=models.CASCADE) 26 | lang = models.CharField(max_length=2) 27 | translation = models.CharField(max_length=20) 28 | 29 | def __str__(self): 30 | return f"{self.greeting} in {self.lang}" 31 | -------------------------------------------------------------------------------- /helloworld/service/admin/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from helloworld.settings_base import * # noqa: F403 5 | 6 | ROOT_URLCONF = "helloworld.service.admin.urls" 7 | 8 | MIDDLEWARE += [ # noqa: F405 9 | "django.contrib.sessions.middleware.SessionMiddleware", 10 | "django.contrib.auth.middleware.AuthenticationMiddleware", 11 | "django.contrib.messages.middleware.MessageMiddleware", 12 | ] 13 | 14 | INSTALLED_APPS = [ 15 | "django.contrib.contenttypes", 16 | "django.contrib.sessions", 17 | "django.contrib.auth", 18 | "django.contrib.admin", 19 | "django.contrib.messages", 20 | "django.contrib.staticfiles", 21 | "helloworld.greet", 22 | "helloworld.person", 23 | "helloworld.translate", 24 | ] 25 | 26 | set_up_database("users") # noqa: F405 27 | set_up_database("greetings") # noqa: F405 28 | 29 | # The admin UI expects to auth against the "default" db, so we alias it here. 30 | DATABASES["default"] = DATABASES["users"] # noqa: F405 31 | 32 | STATIC_URL = "static/" 33 | -------------------------------------------------------------------------------- /helloworld/util/per_app_db_router.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import annotations 5 | 6 | 7 | class PerAppDBRouter: 8 | """A class for routing model queries for an entire app to a specific db. 9 | 10 | This makes it easy to enforce a partitioning of apps across multiple 11 | databases. 12 | """ 13 | 14 | def __init__(self, app_to_db: dict[str, str]) -> None: 15 | self._app_to_db: dict[str, str] = dict(app_to_db) 16 | 17 | def db_for_read(self, model, **hints) -> str | None: 18 | return self._app_to_db.get(model._meta.app_label) 19 | 20 | def db_for_write(self, model, **hints) -> str | None: 21 | return self._app_to_db.get(model._meta.app_label) 22 | 23 | def allow_relation(self, obj1, obj2, **hints) -> bool | None: 24 | # Only allow intra-db relations. 25 | return None 26 | 27 | def allow_migrate( 28 | self, db: str, app_label: str, model_name=None, **hints 29 | ) -> bool | None: 30 | return self._app_to_db.get(app_label) == db 31 | -------------------------------------------------------------------------------- /helloworld/greet/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # Generated by Django 3.2.3 on 2021-05-18 23:56 5 | 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | initial = True 11 | 12 | dependencies = [] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Greeting", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ("slug", models.CharField(max_length=30, unique=True)), 28 | ("start_time", models.TimeField(null=True)), 29 | ("end_time", models.TimeField(null=True)), 30 | ("salutation", models.CharField(max_length=30)), 31 | ], 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | 3 | follow_imports = silent 4 | ignore_missing_imports = True 5 | 6 | # Optionals 7 | no_implicit_optional = True 8 | 9 | # Strictness 10 | allow_untyped_globals = False 11 | allow_redefinition = False 12 | implicit_reexport = False 13 | strict_equality = True 14 | 15 | # Warnings 16 | warn_unused_ignores = True 17 | warn_no_return = True 18 | warn_return_any = True 19 | warn_redundant_casts = True 20 | warn_unreachable = True 21 | 22 | # Error output 23 | show_column_numbers = True 24 | show_error_context = True 25 | show_error_codes = True 26 | show_traceback = True 27 | pretty = True 28 | color_output = True 29 | error_summary = True 30 | 31 | plugins = 32 | mypy_django_plugin.main 33 | 34 | [mypy-colors] 35 | ignore_missing_imports = True 36 | 37 | [mypy-translate] 38 | ignore_missing_imports = True 39 | 40 | [mypy-pytest] 41 | ignore_missing_imports = True 42 | 43 | [mypy.plugins.django-stubs] 44 | # We need to pick one settings module for typechecking the entire repo, including all models. 45 | # Fortunately we have an app (helloworld.service.admin) that depends on all the models. 46 | django_settings_module = "helloworld.service.admin.settings" 47 | 48 | # Turn off mypy for all django migration packages via naming convention. 49 | [mypy-*.migrations.*] 50 | ignore_errors: True 51 | -------------------------------------------------------------------------------- /helloworld/greet/migrations/0002_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import annotations 5 | 6 | from datetime import time 7 | 8 | from django.db import migrations 9 | 10 | 11 | def create_greetings(apps, schema_editor): 12 | Greeting = apps.get_model("greet", "Greeting") 13 | 14 | def create( 15 | slug: str, salutation: str, start_time: str | None, end_time: str | None 16 | ) -> None: 17 | Greeting( 18 | slug=slug, 19 | salutation=salutation, 20 | start_time=time.fromisoformat(start_time) if start_time else None, 21 | end_time=time.fromisoformat(end_time) if end_time else None, 22 | ).save() 23 | 24 | create("hello", "Hello", None, None) 25 | create("howareyou", "How are you", None, None) 26 | create("goodmorning", "Good morning", "05:00:00", "11:59:59") 27 | create("goodevening", "Good evening", "17:00:00", "20:59:59") 28 | create("goodnight", "Good night", "21:00:00", "23:59:59") 29 | 30 | 31 | class Migration(migrations.Migration): 32 | dependencies = [ 33 | ("greet", "0001_initial"), 34 | ] 35 | 36 | operations = [ 37 | migrations.RunPython(create_greetings), 38 | ] 39 | -------------------------------------------------------------------------------- /helloworld/translate/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # Generated by Django 3.2.3 on 2021-05-15 21:45 5 | 6 | import django.db.models.deletion 7 | from django.db import migrations, models 8 | 9 | 10 | class Migration(migrations.Migration): 11 | initial = True 12 | 13 | dependencies = [ 14 | ("greet", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name="Translation", 20 | fields=[ 21 | ( 22 | "id", 23 | models.AutoField( 24 | auto_created=True, 25 | primary_key=True, 26 | serialize=False, 27 | verbose_name="ID", 28 | ), 29 | ), 30 | ("lang", models.CharField(max_length=2)), 31 | ("translation", models.CharField(max_length=20)), 32 | ( 33 | "greeting", 34 | models.ForeignKey( 35 | on_delete=django.db.models.deletion.CASCADE, to="greet.greeting" 36 | ), 37 | ), 38 | ], 39 | ), 40 | migrations.AddConstraint( 41 | model_name="translation", 42 | constraint=models.UniqueConstraint( 43 | fields=("greeting", "lang"), name="greeting_lang" 44 | ), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /helloworld/translate/migrations/0002_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from django.db import migrations 5 | 6 | 7 | def create_greetings(apps, schema_editor): 8 | Greeting = apps.get_model("greet", "Greeting") 9 | Translation = apps.get_model("translate", "Translation") 10 | 11 | def create(slug: str, lang: str, translation: str) -> None: 12 | greeting = Greeting.objects.get(slug=slug) 13 | Translation(greeting=greeting, lang=lang, translation=translation).save() 14 | 15 | create("hello", "en", "Hello") 16 | create("goodmorning", "en", "Good morning") 17 | create("goodevening", "en", "Good evening") 18 | create("goodnight", "en", "Good night") 19 | 20 | create("hello", "es", "Hola") 21 | create("goodmorning", "es", "Buenos días") 22 | create("goodevening", "es", "Buenas tardes") 23 | create("goodnight", "es", "Buenas noches") 24 | 25 | create("hello", "fr", "Allo") 26 | create("goodmorning", "fr", "Bonjour") 27 | create("goodevening", "fr", "Bonsoir") 28 | create("goodnight", "fr", "Bonne nuit") 29 | 30 | create("hello", "de", "Hallo") 31 | create("goodmorning", "de", "Guten Morgen") 32 | create("goodevening", "de", "Guten Abend") 33 | create("goodnight", "de", "Gute Nacht") 34 | 35 | 36 | class Migration(migrations.Migration): 37 | dependencies = [ 38 | ("translate", "0001_initial"), 39 | ("greet", "0002_data"), 40 | ] 41 | 42 | operations = [ 43 | migrations.RunPython(create_greetings), 44 | ] 45 | -------------------------------------------------------------------------------- /helloworld/ui/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | import datetime 5 | 6 | import requests 7 | from django.http import Http404, HttpResponse 8 | from django.shortcuts import render 9 | 10 | from helloworld.util.backend import get_backend_url 11 | 12 | 13 | def query_backend(url: str) -> str: 14 | response = requests.get(url) 15 | if response.status_code == 404: 16 | raise Http404(f"Backend error: {url}") 17 | # All other backend errors should become 500 errors for the frontend. 18 | response.raise_for_status() 19 | return response.text 20 | 21 | 22 | def index(request) -> HttpResponse: 23 | person_slug = request.GET.get("person", "") 24 | lang = request.GET.get("lang", "en") 25 | time_of_day = request.GET.get("tod", "") 26 | 27 | # Get person's full name. 28 | name = query_backend( 29 | f"{get_backend_url('helloworld.service.user')}/person/{person_slug}/" 30 | ) 31 | 32 | # Get greeting. 33 | time_of_day = time_of_day or datetime.datetime.now().time().strftime("%H:%M:%S") 34 | greeting_slug = query_backend( 35 | f"{get_backend_url('helloworld.service.welcome')}/greet/tod/{time_of_day}/" 36 | ) 37 | if not greeting_slug: 38 | # Fall back to hello if no time-of-day-specific greeting found,. 39 | greeting_slug = "hello" 40 | 41 | greeting = query_backend( 42 | f"{get_backend_url('helloworld.service.welcome')}/translate/{greeting_slug}/{lang}/" 43 | ) 44 | return render( 45 | request, "helloworld/ui/index.html", {"greeting": greeting, "name": name} 46 | ) 47 | -------------------------------------------------------------------------------- /helloworld/util/service.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | import os 5 | import sys 6 | from pathlib import PurePath 7 | 8 | from helloworld.util.discovery import get_dev_port 9 | 10 | 11 | class Service: 12 | def __init__(self, name_or_file: str): 13 | if os.path.sep in name_or_file: 14 | # This is the __file__ of the caller, so compute the service name. 15 | rparts = list(reversed(PurePath(name_or_file).parts)) 16 | self._name = ".".join(reversed(rparts[1 : rparts.index("helloworld") + 1])) 17 | else: 18 | self._name = name_or_file 19 | 20 | def run_gunicorn(self): 21 | # If no args were provided, fill them in for convenience. 22 | if len(sys.argv) == 1: 23 | sys.argv.extend( 24 | ["--config", "python:helloworld.gunicorn_conf", "helloworld.wsgi"] 25 | ) 26 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"{self._name}.settings") 27 | from gunicorn.app.wsgiapp import run 28 | 29 | sys.exit(run()) 30 | 31 | def run_manage(self): 32 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"{self._name}.settings") 33 | args = sys.argv 34 | if len(sys.argv) == 2 and sys.argv[1] == "runserver": 35 | # We rely on Pants's reloading, so turn off Django's (which doesn't interact 36 | # well with Pex: Pex's re-exec logic causes Django's re-exec to misdetect its 37 | # entry point, see https://code.djangoproject.com/ticket/32314). 38 | # TODO: Some way to detect that we're in a `pants run`, and only set --noreload 39 | # in that case, so that users can run manage.py directly if they want to. 40 | args += ["--noreload"] 41 | # If no port was provided, use the dev port for this service. 42 | dev_port = get_dev_port(self._name) 43 | args += [f"{dev_port}"] 44 | from django.core.management import execute_from_command_line 45 | 46 | execute_from_command_line(args) 47 | -------------------------------------------------------------------------------- /helloworld/settings_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import annotations 5 | 6 | import os 7 | from typing import Any 8 | 9 | from helloworld.util.per_app_db_router import PerAppDBRouter 10 | 11 | HELLOWORLD_MODE = os.environ.get("HELLOWORLD_MODE", "DEV") 12 | 13 | SECRET_KEY = "DEV_SECURITY_KEY" 14 | 15 | DEBUG = True 16 | 17 | ALLOWED_HOSTS: list[str] = [] 18 | 19 | DEFAULT_AUTO_FIELD = "django.db.models.AutoField" 20 | 21 | # Application definition 22 | 23 | MIDDLEWARE = [ 24 | "django.middleware.common.CommonMiddleware", 25 | ] 26 | 27 | TEMPLATES = [ 28 | { 29 | "BACKEND": "django.template.backends.django.DjangoTemplates", 30 | "DIRS": [], 31 | "APP_DIRS": True, 32 | "OPTIONS": { 33 | "context_processors": [ 34 | "django.contrib.auth.context_processors.auth", 35 | "django.contrib.messages.context_processors.messages", 36 | "django.template.context_processors.debug", 37 | "django.template.context_processors.request", 38 | ], 39 | }, 40 | }, 41 | ] 42 | 43 | DATABASES: dict[str, Any] = { 44 | # Django chokes if 'default' isn't present at all. But it can be set to an empty dict, which 45 | # will be treated as a dummy db. 46 | "default": {} 47 | } 48 | 49 | 50 | def set_up_database(db_name: str): 51 | """Set a service up to connect to a named db.""" 52 | # TODO: Consult HELLOWORLD_MODE to distinguish dev/staging/prod dbs. 53 | DATABASES[db_name] = { 54 | "ENGINE": "django.db.backends.sqlite3", 55 | "NAME": f"{db_name}.sqlite3", 56 | } 57 | 58 | 59 | DATABASE_ROUTERS = [ 60 | PerAppDBRouter( 61 | { 62 | "contenttypes": "users", 63 | "sessions": "users", 64 | "auth": "users", 65 | "admin": "users", 66 | "person": "users", 67 | "greet": "greetings", 68 | "translate": "greetings", 69 | } 70 | ) 71 | ] 72 | 73 | LANGUAGE_CODE = "en-us" 74 | 75 | TIME_ZONE = "UTC" 76 | 77 | USE_I18N = True 78 | 79 | USE_L10N = True 80 | 81 | USE_TZ = True 82 | 83 | WSGI_APPLICATION = "helloworld.wsgi.application" 84 | -------------------------------------------------------------------------------- /pants.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | [GLOBAL] 5 | pants_version = "2.29.0" 6 | 7 | backend_packages.add = [ 8 | 'pants.backend.python', 9 | 'pants.backend.python.lint.docformatter', 10 | 'pants.backend.python.lint.black', 11 | 'pants.backend.python.lint.flake8', 12 | 'pants.backend.python.lint.isort', 13 | 'pants.backend.python.typecheck.mypy', 14 | ] 15 | 16 | [anonymous-telemetry] 17 | enabled = true 18 | repo_id = "37798A99-096B-4B79-8D0F-30D4D1A1ECD1" 19 | 20 | [source] 21 | # The Python source root is the repo root. See https://www.pantsbuild.org/docs/source-roots. 22 | root_patterns = ["/"] 23 | 24 | [python] 25 | # The default interpreter compatibility for code in this repo. Individual targets can override 26 | # this with the `compatibility` field. See 27 | # https://www.pantsbuild.org/docs/python-interpreter-compatibility. 28 | interpreter_constraints = [">=3.12,<3.13"] 29 | # Use a lockfile. See https://www.pantsbuild.org/docs/python-third-party-dependencies. 30 | enable_resolves = true 31 | resolves = { python-default = "lockfiles/python-default.lock" } 32 | 33 | [python-bootstrap] 34 | # We search for interpreters on both on the $PATH and in the `$(pyenv root)/versions` folder. 35 | # Note the gotcha that the order of entries does not matter in this option, as Pants will consider 36 | # all interpreters it finds and select a compatible one. 37 | # So, if you're using macOS, you may want to leave off the entry to avoid using the 38 | # problematic system Pythons. See 39 | # https://www.pantsbuild.org/docs/python-interpreter-compatibility#changing-the-interpreter-search-path. 40 | search_path = ["", ""] 41 | 42 | [python-infer] 43 | # Infer dependencies from strings that look like module/class names, such as are often 44 | # found in settings.py, where dependencies are enumerated as strings and not directly imported. 45 | string_imports = true 46 | use_rust_parser = true 47 | 48 | [pytest] 49 | install_from_resolve = "python-default" 50 | requirements = ["pytest-django"] 51 | 52 | execution_slot_var = "PANTS_EXECUTION_SLOT" 53 | 54 | [mypy] 55 | install_from_resolve = "python-default" 56 | requirements = ["mypy", "django-stubs"] 57 | interpreter_constraints = [">=3.12,<3.13"] 58 | -------------------------------------------------------------------------------- /.github/workflows/pants.yaml: -------------------------------------------------------------------------------- 1 | # See https://www.pantsbuild.org/stable/docs/using-pants/using-pants-in-ci for tips on how to set up your CI with Pants. 2 | 3 | name: Pants 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | org-check: 9 | name: Check GitHub Organization 10 | if: ${{ github.repository_owner == 'pantsbuild' }} 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - name: Noop 14 | run: "true" 15 | build: 16 | name: Perform CI Checks 17 | needs: org-check 18 | env: 19 | PANTS_CONFIG_FILES: pants.ci.toml 20 | runs-on: ubuntu-24.04 21 | strategy: 22 | matrix: 23 | python-version: [3.12] 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - uses: pantsbuild/actions/init-pants@v5-scie-pants 30 | # This action bootstraps pants and manages 2-3 GHA caches. 31 | # See: github.com/pantsbuild/actions/tree/main/init-pants/ 32 | with: 33 | # v0 makes it easy to bust the cache if needed 34 | # just increase the integer to start with a fresh cache 35 | gha-cache-key: v0 36 | # The Python backend uses named_caches for Pip/PEX state, 37 | # so it is appropriate to invalidate on lockfile changes. 38 | named-caches-hash: ${{ hashFiles('lockfiles/*') }} 39 | # If you're not using a fine-grained remote caching service (see https://www.pantsbuild.org/docs/remote-caching), 40 | # then you may also want to preserve the local Pants cache (lmdb_store). However this must invalidate for 41 | # changes to any file that can affect the build, so may not be practical in larger repos. 42 | # A remote cache service integrates with Pants's fine-grained invalidation and avoids these problems. 43 | cache-lmdb-store: 'true' # defaults to 'false' 44 | # Note that named_caches and lmdb_store falls back to partial restore keys which 45 | # may give a useful partial result that will save time over completely clean state, 46 | # but will cause the cache entry to grow without bound over time. 47 | # See https://www.pantsbuild.org/stable/docs/using-pants/using-pants-in-ci for tips on how to periodically clean it up. 48 | # Alternatively you change gha-cache-key to ignore old caches. 49 | - name: Bootstrap Pants 50 | run: | 51 | pants --version 52 | - name: Check BUILD files 53 | run: | 54 | pants tailor --check update-build-files --check :: 55 | - name: Lint and typecheck 56 | run: | 57 | pants lint check :: 58 | - name: Test 59 | run: | 60 | pants test :: 61 | - name: Package / Run 62 | run: | 63 | # We also smoke test that our release process will work by running `package`. 64 | pants package :: 65 | - name: Upload pants log 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: pants-log 69 | path: .pants.d/pants.log 70 | if: always() # We want the log even on failures. 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # example-django 2 | 3 | An example repository to demonstrate Django support in Pants. 4 | 5 | The demo consists of a set of services that implement an elaborate "Hello, World" site. 6 | This repo shows how Pants can support multiple Django services in a single repo, with 7 | minimal copypasta. 8 | 9 | The layout in this repos enforces a strict distinction between Django apps (reusable units of 10 | functionality such as models and related views), vs. services ("projects" in Django parlance) - 11 | deployable services composed of multiple apps and typically attached to one or more database instances. 12 | 13 | See [pantsbuild.org](https://www.pantsbuild.org/) for much more detailed documentation about Pants. 14 | 15 | See [here](https://github.com/pantsbuild/example-python) for an example of basic Python support in Pants. 16 | 17 | See [here](https://www.pantsbuild.org/docs/installation) for how to install the `pants` binary. 18 | 19 | ## Architecture 20 | 21 | We utilize a flexible "database-first" architecture: Since migrating production databases 22 | is significantly harder and riskier than modifying stateless services, our architecture treats 23 | the databases as standalone logical entities not subservient to any app or service. 24 | "Code is for now, data is forever." 25 | 26 | - The models in each app map to one of the logical databases, via a custom 27 | [database router](helloworld/util/per_app_db_router.py). 28 | - The settings for each server map the logical databases required by its apps to physical databases. 29 | 30 | If it makes sense to do so, different services can reuse the same app in different databases, by 31 | pointing the same logical database at different physical databases in each service. 32 | 33 | The only constraint is that if a model in one app reference a model in another app via foreign key 34 | then both apps must be mapped to the same logical db. You can see an example of this in 35 | [helloworld/translate/models.py](helloworld/translate/models.py). 36 | 37 | This architecture allows services to evolve in a flexible way as more apps and more functionality 38 | are developed. 39 | 40 | ## Databases 41 | 42 | In order to demonstrate multi-db configurations, there are two logical databases: 43 | 44 | - `users`: User-related data. 45 | - `greetings`: Greeting-related data. 46 | 47 | Currently the services map these logical databases to sqlite dbs. 48 | We plan to add examples of using PostgreSQL. 49 | 50 | ## Services 51 | 52 | There are four distinct services, defined under [`helloworld/service`](helloworld/service): 53 | 54 | - `helloworld.services.admin`: Runs the Django admin UI. 55 | - `helloworld.services.frontend`: Serves end user requests. 56 | - `helloworld.services.user`: A backend service that provides user data. 57 | - `helloworld.services.welcome`: A backend service that generates greetings. 58 | 59 | Each service has its own `manage.py`, `gunicorn.py`, `urls.py` and `settings.py`. 60 | The `settings.py` inherits most of its settings from [`helloworld/settings_base.py`](helloworld/settings_base.py) 61 | and adds anything specific to that service. 62 | 63 | Note that we make no argument for or against microservices vs. monolithic services as a deployment 64 | architecture. We *do* make an argument in favor of a monorepo over multiple repos as a codebase architecture, 65 | and this repo demonstrates the utility of a monorepo, especially if you deploy multiple services. 66 | 67 | ## Apps 68 | 69 | The services are composed of four Django apps: 70 | 71 | - `helloworld.greet`: Functionality related to selecting a greeting. 72 | - `helloworld.person`: Functionality related to identifying the person to greet. 73 | - `helloworld.translate`: Functionality related to translating greetings into various languages. 74 | - `helloworld.ui`: Functionality related to rendering greetings to the end user. 75 | 76 | ## To view the frontend: 77 | 78 | In three terminals run: 79 | - `pants --concurrent run helloworld/service/frontend/manage.py -- runserver` 80 | - `pants --concurrent run helloworld/service/user/manage.py -- runserver` 81 | - `pants --concurrent run helloworld/service/welcome/manage.py -- runserver` 82 | 83 | And visit this URL in a browser: [http://127.0.0.1:8000/?person=sherlock&lang=es]() . 84 | You will have to first set up a database and run migrations, of course. 85 | 86 | ## Useful Pants commands 87 | 88 | To run all tests: 89 | 90 | ``` 91 | pants test :: 92 | ``` 93 | 94 | To run formatters and linters: 95 | 96 | ``` 97 | pants fmt lint :: 98 | ``` 99 | 100 | To run typechecking: 101 | 102 | ``` 103 | pants check :: 104 | ``` 105 | 106 | To build deployable gunicorn .pex files for all services: 107 | 108 | ``` 109 | pants package :: 110 | ``` 111 | 112 | ## manage.py 113 | 114 | To run management commands for a service, use that service's `manage.py`, e.g., 115 | 116 | ``` 117 | pants run helloworld/service/admin/manage.py -- runserver 118 | ``` 119 | 120 | Note that for `runserver`, each dev server will run on its own port, see DEV_PORTS in 121 | [`helloworld/util/discovery.py`](helloworld/util/discovery.py). 122 | 123 | Also, with `runserver` we [turn off](helloworld/util/service.py#L40) Django's autoreloader. 124 | Instead, we rely on Pants's own file-watching, by setting `restartable=True` on `manage.py`. 125 | Pants will correctly restart servers in situations where Django cannot, such as changes to 126 | BUILD files, to `.proto` files, or to 3rdparty dependencies. 127 | 128 | To run migrations, it's best to use the admin service's manage.py, as it has access to 129 | all apps: 130 | 131 | ``` 132 | pants run helloworld/service/admin/manage.py -- migrate --database=users 133 | pants run helloworld/service/admin/manage.py -- migrate --database=greetings 134 | ``` 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lockfiles/python-default.lock: -------------------------------------------------------------------------------- 1 | // This lockfile was autogenerated by Pants. To regenerate, run: 2 | // 3 | // pants generate-lockfiles --resolve=python-default 4 | // 5 | // --- BEGIN PANTS LOCKFILE METADATA: DO NOT EDIT OR REMOVE --- 6 | // { 7 | // "version": 4, 8 | // "valid_for_interpreter_constraints": [ 9 | // "CPython<3.13,>=3.12" 10 | // ], 11 | // "generated_with_requirements": [ 12 | // "ansicolors>=1.0.2", 13 | // "django-debug-toolbar<4,>=3.8.0", 14 | // "django-stubs~=4.2.7", 15 | // "django<4,>=3.2.13", 16 | // "gunicorn>=20.1.0", 17 | // "mypy==1.18.1", 18 | // "protobuf>=3.11.3", 19 | // "pytest-django<5,>=4", 20 | // "pytest>=6.0.1", 21 | // "requests>=2.25.1", 22 | // "setuptools>=42.0.0", 23 | // "translate>=3.2.1", 24 | // "types-requests>=2.25.1" 25 | // ], 26 | // "manylinux": "manylinux2014", 27 | // "requirement_constraints": [], 28 | // "only_binary": [], 29 | // "no_binary": [], 30 | // "excludes": [], 31 | // "overrides": [] 32 | // } 33 | // --- END PANTS LOCKFILE METADATA --- 34 | 35 | { 36 | "allow_builds": true, 37 | "allow_prereleases": false, 38 | "allow_wheels": true, 39 | "build_isolation": true, 40 | "constraints": [], 41 | "elide_unused_requires_dist": false, 42 | "excluded": [], 43 | "locked_resolves": [ 44 | { 45 | "locked_requirements": [ 46 | { 47 | "artifacts": [ 48 | { 49 | "algorithm": "sha256", 50 | "hash": "00d2dde5a675579325902536738dd27e4fac1fd68f773fe36c21044eb559e187", 51 | "url": "https://files.pythonhosted.org/packages/53/18/a56e2fe47b259bb52201093a3a9d4a32014f9d85071ad07e9d60600890ca/ansicolors-1.1.8-py2.py3-none-any.whl" 52 | }, 53 | { 54 | "algorithm": "sha256", 55 | "hash": "99f94f5e3348a0bcd43c82e5fc4414013ccc19d70bd939ad71e0133ce9c372e0", 56 | "url": "https://files.pythonhosted.org/packages/76/31/7faed52088732704523c259e24c26ce6f2f33fbeff2ff59274560c27628e/ansicolors-1.1.8.zip" 57 | } 58 | ], 59 | "project_name": "ansicolors", 60 | "requires_dists": [], 61 | "requires_python": null, 62 | "version": "1.1.8" 63 | }, 64 | { 65 | "artifacts": [ 66 | { 67 | "algorithm": "sha256", 68 | "hash": "aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734", 69 | "url": "https://files.pythonhosted.org/packages/17/9c/fc2331f538fbf7eedba64b2052e99ccf9ba9d6888e2f41441ee28847004b/asgiref-3.10.0-py3-none-any.whl" 70 | }, 71 | { 72 | "algorithm": "sha256", 73 | "hash": "d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e", 74 | "url": "https://files.pythonhosted.org/packages/46/08/4dfec9b90758a59acc6be32ac82e98d1fbfc321cb5cfa410436dbacf821c/asgiref-3.10.0.tar.gz" 75 | } 76 | ], 77 | "project_name": "asgiref", 78 | "requires_dists": [ 79 | "mypy>=1.14.0; extra == \"tests\"", 80 | "pytest-asyncio; extra == \"tests\"", 81 | "pytest; extra == \"tests\"", 82 | "typing_extensions>=4; python_version < \"3.11\"" 83 | ], 84 | "requires_python": ">=3.9", 85 | "version": "3.10.0" 86 | }, 87 | { 88 | "artifacts": [ 89 | { 90 | "algorithm": "sha256", 91 | "hash": "0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", 92 | "url": "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl" 93 | }, 94 | { 95 | "algorithm": "sha256", 96 | "hash": "47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", 97 | "url": "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz" 98 | } 99 | ], 100 | "project_name": "certifi", 101 | "requires_dists": [], 102 | "requires_python": ">=3.7", 103 | "version": "2025.10.5" 104 | }, 105 | { 106 | "artifacts": [ 107 | { 108 | "algorithm": "sha256", 109 | "hash": "ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", 110 | "url": "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl" 111 | }, 112 | { 113 | "algorithm": "sha256", 114 | "hash": "42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", 115 | "url": "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl" 116 | }, 117 | { 118 | "algorithm": "sha256", 119 | "hash": "1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", 120 | "url": "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl" 121 | }, 122 | { 123 | "algorithm": "sha256", 124 | "hash": "cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", 125 | "url": "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl" 126 | }, 127 | { 128 | "algorithm": "sha256", 129 | "hash": "0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", 130 | "url": "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl" 131 | }, 132 | { 133 | "algorithm": "sha256", 134 | "hash": "3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", 135 | "url": "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl" 136 | }, 137 | { 138 | "algorithm": "sha256", 139 | "hash": "6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", 140 | "url": "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz" 141 | }, 142 | { 143 | "algorithm": "sha256", 144 | "hash": "027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", 145 | "url": "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl" 146 | }, 147 | { 148 | "algorithm": "sha256", 149 | "hash": "320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", 150 | "url": "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl" 151 | }, 152 | { 153 | "algorithm": "sha256", 154 | "hash": "e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", 155 | "url": "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl" 156 | }, 157 | { 158 | "algorithm": "sha256", 159 | "hash": "c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", 160 | "url": "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl" 161 | } 162 | ], 163 | "project_name": "charset-normalizer", 164 | "requires_dists": [], 165 | "requires_python": ">=3.7", 166 | "version": "3.4.3" 167 | }, 168 | { 169 | "artifacts": [ 170 | { 171 | "algorithm": "sha256", 172 | "hash": "9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", 173 | "url": "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl" 174 | }, 175 | { 176 | "algorithm": "sha256", 177 | "hash": "e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", 178 | "url": "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz" 179 | } 180 | ], 181 | "project_name": "click", 182 | "requires_dists": [ 183 | "colorama; platform_system == \"Windows\"" 184 | ], 185 | "requires_python": ">=3.10", 186 | "version": "8.3.0" 187 | }, 188 | { 189 | "artifacts": [ 190 | { 191 | "algorithm": "sha256", 192 | "hash": "a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38", 193 | "url": "https://files.pythonhosted.org/packages/30/8e/cc23c762c5dcd1d367d73cf006a326e0df2bd0e785cba18b658b39904c1e/Django-3.2.25-py3-none-any.whl" 194 | }, 195 | { 196 | "algorithm": "sha256", 197 | "hash": "7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777", 198 | "url": "https://files.pythonhosted.org/packages/ec/68/0e744f07b57bfdf99abbb6b3eb14fcba188867021c05f4a104e04f6d56b8/Django-3.2.25.tar.gz" 199 | } 200 | ], 201 | "project_name": "django", 202 | "requires_dists": [ 203 | "argon2-cffi>=19.1.0; extra == \"argon2\"", 204 | "asgiref<4,>=3.3.2", 205 | "bcrypt; extra == \"bcrypt\"", 206 | "pytz", 207 | "sqlparse>=0.2.2" 208 | ], 209 | "requires_python": ">=3.6", 210 | "version": "3.2.25" 211 | }, 212 | { 213 | "artifacts": [ 214 | { 215 | "algorithm": "sha256", 216 | "hash": "879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478", 217 | "url": "https://files.pythonhosted.org/packages/c7/c3/fd81e84ce86904019112428c690a06ab19c66742c917a898dcf9da6ae166/django_debug_toolbar-3.8.1-py3-none-any.whl" 218 | }, 219 | { 220 | "algorithm": "sha256", 221 | "hash": "24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27", 222 | "url": "https://files.pythonhosted.org/packages/80/53/1d1e3bbef3c6f5820f838a6a6a8e795a3c86323f9dbf2f8a89de78477972/django_debug_toolbar-3.8.1.tar.gz" 223 | } 224 | ], 225 | "project_name": "django-debug-toolbar", 226 | "requires_dists": [ 227 | "django>=3.2.4", 228 | "sqlparse>=0.2" 229 | ], 230 | "requires_python": ">=3.7", 231 | "version": "3.8.1" 232 | }, 233 | { 234 | "artifacts": [ 235 | { 236 | "algorithm": "sha256", 237 | "hash": "4cf4de258fa71adc6f2799e983091b9d46cfc67c6eebc68fe111218c9a62b3b8", 238 | "url": "https://files.pythonhosted.org/packages/31/33/a4024662b31999841bc18ef845a790f6b6a9ad50f5b2aceb029896612d17/django_stubs-4.2.7-py3-none-any.whl" 239 | }, 240 | { 241 | "algorithm": "sha256", 242 | "hash": "8ccd2ff4ee5adf22b9e3b7b1a516d2e1c2191e9d94e672c35cc2bc3dd61e0f6b", 243 | "url": "https://files.pythonhosted.org/packages/1c/1f/1b1da357d37cbbf21496c17aad5a55416c7c37944a909c4a19ddd61994df/django-stubs-4.2.7.tar.gz" 244 | } 245 | ], 246 | "project_name": "django-stubs", 247 | "requires_dists": [ 248 | "django", 249 | "django-stubs-ext>=4.2.7", 250 | "mypy~=1.7.0; extra == \"compatible-mypy\"", 251 | "tomli; python_version < \"3.11\"", 252 | "types-PyYAML", 253 | "types-pytz", 254 | "typing-extensions" 255 | ], 256 | "requires_python": ">=3.8", 257 | "version": "4.2.7" 258 | }, 259 | { 260 | "artifacts": [ 261 | { 262 | "algorithm": "sha256", 263 | "hash": "9b4b8ac9d32f7e6c304fd05477f8688fae6ed57f6a0f9f4d074f9e55b5a3da14", 264 | "url": "https://files.pythonhosted.org/packages/e0/fe/a85a105fddffadb4a8d50e500caeee87d836b679d51a19d52dfa0cc6c660/django_stubs_ext-5.2.5-py3-none-any.whl" 265 | }, 266 | { 267 | "algorithm": "sha256", 268 | "hash": "ecc628df29d36cede638567c4e33ff485dd7a99f1552ad0cece8c60e9c3a8872", 269 | "url": "https://files.pythonhosted.org/packages/16/94/c9b8f4c47084a0fa666da9066c36771098101932688adf2c17a40fab79c2/django_stubs_ext-5.2.5.tar.gz" 270 | } 271 | ], 272 | "project_name": "django-stubs-ext", 273 | "requires_dists": [ 274 | "django", 275 | "typing-extensions" 276 | ], 277 | "requires_python": ">=3.10", 278 | "version": "5.2.5" 279 | }, 280 | { 281 | "artifacts": [ 282 | { 283 | "algorithm": "sha256", 284 | "hash": "ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", 285 | "url": "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl" 286 | }, 287 | { 288 | "algorithm": "sha256", 289 | "hash": "f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", 290 | "url": "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz" 291 | } 292 | ], 293 | "project_name": "gunicorn", 294 | "requires_dists": [ 295 | "coverage; extra == \"testing\"", 296 | "eventlet!=0.36.0,>=0.24.1; extra == \"eventlet\"", 297 | "eventlet; extra == \"testing\"", 298 | "gevent; extra == \"testing\"", 299 | "gevent>=1.4.0; extra == \"gevent\"", 300 | "importlib-metadata; python_version < \"3.8\"", 301 | "packaging", 302 | "pytest-cov; extra == \"testing\"", 303 | "pytest; extra == \"testing\"", 304 | "setproctitle; extra == \"setproctitle\"", 305 | "tornado>=0.2; extra == \"tornado\"" 306 | ], 307 | "requires_python": ">=3.7", 308 | "version": "23.0.0" 309 | }, 310 | { 311 | "artifacts": [ 312 | { 313 | "algorithm": "sha256", 314 | "hash": "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", 315 | "url": "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl" 316 | }, 317 | { 318 | "algorithm": "sha256", 319 | "hash": "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", 320 | "url": "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz" 321 | } 322 | ], 323 | "project_name": "idna", 324 | "requires_dists": [ 325 | "flake8>=7.1.1; extra == \"all\"", 326 | "mypy>=1.11.2; extra == \"all\"", 327 | "pytest>=8.3.2; extra == \"all\"", 328 | "ruff>=0.6.2; extra == \"all\"" 329 | ], 330 | "requires_python": ">=3.6", 331 | "version": "3.10" 332 | }, 333 | { 334 | "artifacts": [ 335 | { 336 | "algorithm": "sha256", 337 | "hash": "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", 338 | "url": "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl" 339 | }, 340 | { 341 | "algorithm": "sha256", 342 | "hash": "3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", 343 | "url": "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz" 344 | } 345 | ], 346 | "project_name": "iniconfig", 347 | "requires_dists": [], 348 | "requires_python": ">=3.8", 349 | "version": "2.1.0" 350 | }, 351 | { 352 | "artifacts": [ 353 | { 354 | "algorithm": "sha256", 355 | "hash": "bf9f8b0003c94f34e141553b7ec0a02876297c06955f5efea49afbc9f85303a9", 356 | "url": "https://files.pythonhosted.org/packages/24/54/044ecbf4eeba3f3feee70d496f34f397dc20714cdb80dfa8bc01e7409f88/libretranslatepy-2.1.1-py3-none-any.whl" 357 | }, 358 | { 359 | "algorithm": "sha256", 360 | "hash": "3f28e1b990ba5f514ae215c08ace0c4e2327eeccaa356983aefbca3a25ecc568", 361 | "url": "https://files.pythonhosted.org/packages/ef/ec/9b31f896697e164204ac4a31c9cfc31c0fee97ca5b97829d24a9cfc7d33c/libretranslatepy-2.1.1.tar.gz" 362 | } 363 | ], 364 | "project_name": "libretranslatepy", 365 | "requires_dists": [], 366 | "requires_python": null, 367 | "version": "2.1.1" 368 | }, 369 | { 370 | "artifacts": [ 371 | { 372 | "algorithm": "sha256", 373 | "hash": "6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", 374 | "url": "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl" 375 | }, 376 | { 377 | "algorithm": "sha256", 378 | "hash": "fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", 379 | "url": "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl" 380 | }, 381 | { 382 | "algorithm": "sha256", 383 | "hash": "064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", 384 | "url": "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl" 385 | }, 386 | { 387 | "algorithm": "sha256", 388 | "hash": "e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", 389 | "url": "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl" 390 | }, 391 | { 392 | "algorithm": "sha256", 393 | "hash": "2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", 394 | "url": "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl" 395 | }, 396 | { 397 | "algorithm": "sha256", 398 | "hash": "700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", 399 | "url": "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl" 400 | }, 401 | { 402 | "algorithm": "sha256", 403 | "hash": "cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", 404 | "url": "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz" 405 | }, 406 | { 407 | "algorithm": "sha256", 408 | "hash": "c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", 409 | "url": "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl" 410 | }, 411 | { 412 | "algorithm": "sha256", 413 | "hash": "6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", 414 | "url": "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl" 415 | }, 416 | { 417 | "algorithm": "sha256", 418 | "hash": "90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", 419 | "url": "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl" 420 | }, 421 | { 422 | "algorithm": "sha256", 423 | "hash": "c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", 424 | "url": "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl" 425 | }, 426 | { 427 | "algorithm": "sha256", 428 | "hash": "57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", 429 | "url": "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl" 430 | }, 431 | { 432 | "algorithm": "sha256", 433 | "hash": "65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", 434 | "url": "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" 435 | }, 436 | { 437 | "algorithm": "sha256", 438 | "hash": "6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", 439 | "url": "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl" 440 | }, 441 | { 442 | "algorithm": "sha256", 443 | "hash": "a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", 444 | "url": "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl" 445 | }, 446 | { 447 | "algorithm": "sha256", 448 | "hash": "a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", 449 | "url": "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl" 450 | } 451 | ], 452 | "project_name": "lxml", 453 | "requires_dists": [ 454 | "BeautifulSoup4; extra == \"htmlsoup\"", 455 | "cssselect>=0.7; extra == \"cssselect\"", 456 | "html5lib; extra == \"html5\"", 457 | "lxml_html_clean; extra == \"html-clean\"" 458 | ], 459 | "requires_python": ">=3.8", 460 | "version": "6.0.2" 461 | }, 462 | { 463 | "artifacts": [ 464 | { 465 | "algorithm": "sha256", 466 | "hash": "b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e", 467 | "url": "https://files.pythonhosted.org/packages/e0/1d/4b97d3089b48ef3d904c9ca69fab044475bd03245d878f5f0b3ea1daf7ce/mypy-1.18.1-py3-none-any.whl" 468 | }, 469 | { 470 | "algorithm": "sha256", 471 | "hash": "9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9", 472 | "url": "https://files.pythonhosted.org/packages/14/a3/931e09fc02d7ba96da65266884da4e4a8806adcdb8a57faaacc6edf1d538/mypy-1.18.1.tar.gz" 473 | }, 474 | { 475 | "algorithm": "sha256", 476 | "hash": "8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d", 477 | "url": "https://files.pythonhosted.org/packages/32/bb/92642a9350fc339dd9dcefcf6862d171b52294af107d521dce075f32f298/mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl" 478 | }, 479 | { 480 | "algorithm": "sha256", 481 | "hash": "7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e", 482 | "url": "https://files.pythonhosted.org/packages/90/83/235606c8b6d50a8eba99773add907ce1d41c068edb523f81eb0d01603a83/mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl" 483 | }, 484 | { 485 | "algorithm": "sha256", 486 | "hash": "5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2", 487 | "url": "https://files.pythonhosted.org/packages/ca/25/4e2ce00f8d15b99d0c68a2536ad63e9eac033f723439ef80290ec32c1ff5/mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl" 488 | }, 489 | { 490 | "algorithm": "sha256", 491 | "hash": "fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5", 492 | "url": "https://files.pythonhosted.org/packages/cd/ee/38d01db91c198fb6350025d28f9719ecf3c8f2c55a0094bfbf3ef478cc9a/mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl" 493 | }, 494 | { 495 | "algorithm": "sha256", 496 | "hash": "502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9", 497 | "url": "https://files.pythonhosted.org/packages/e7/14/1c3f54d606cb88a55d1567153ef3a8bc7b74702f2ff5eb64d0994f9e49cb/mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl" 498 | } 499 | ], 500 | "project_name": "mypy", 501 | "requires_dists": [ 502 | "lxml; extra == \"reports\"", 503 | "mypy_extensions>=1.0.0", 504 | "orjson; extra == \"faster-cache\"", 505 | "pathspec>=0.9.0", 506 | "pip; extra == \"install-types\"", 507 | "psutil>=4.0; extra == \"dmypy\"", 508 | "setuptools>=50; extra == \"mypyc\"", 509 | "tomli>=1.1.0; python_version < \"3.11\"", 510 | "typing_extensions>=4.6.0" 511 | ], 512 | "requires_python": ">=3.9", 513 | "version": "1.18.1" 514 | }, 515 | { 516 | "artifacts": [ 517 | { 518 | "algorithm": "sha256", 519 | "hash": "1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", 520 | "url": "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl" 521 | }, 522 | { 523 | "algorithm": "sha256", 524 | "hash": "52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", 525 | "url": "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz" 526 | } 527 | ], 528 | "project_name": "mypy-extensions", 529 | "requires_dists": [], 530 | "requires_python": ">=3.8", 531 | "version": "1.1.0" 532 | }, 533 | { 534 | "artifacts": [ 535 | { 536 | "algorithm": "sha256", 537 | "hash": "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", 538 | "url": "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl" 539 | }, 540 | { 541 | "algorithm": "sha256", 542 | "hash": "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", 543 | "url": "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz" 544 | } 545 | ], 546 | "project_name": "packaging", 547 | "requires_dists": [], 548 | "requires_python": ">=3.8", 549 | "version": "25.0" 550 | }, 551 | { 552 | "artifacts": [ 553 | { 554 | "algorithm": "sha256", 555 | "hash": "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", 556 | "url": "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl" 557 | }, 558 | { 559 | "algorithm": "sha256", 560 | "hash": "a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", 561 | "url": "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz" 562 | } 563 | ], 564 | "project_name": "pathspec", 565 | "requires_dists": [], 566 | "requires_python": ">=3.8", 567 | "version": "0.12.1" 568 | }, 569 | { 570 | "artifacts": [ 571 | { 572 | "algorithm": "sha256", 573 | "hash": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", 574 | "url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl" 575 | }, 576 | { 577 | "algorithm": "sha256", 578 | "hash": "7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", 579 | "url": "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz" 580 | } 581 | ], 582 | "project_name": "pluggy", 583 | "requires_dists": [ 584 | "coverage; extra == \"testing\"", 585 | "pre-commit; extra == \"dev\"", 586 | "pytest-benchmark; extra == \"testing\"", 587 | "pytest; extra == \"testing\"", 588 | "tox; extra == \"dev\"" 589 | ], 590 | "requires_python": ">=3.9", 591 | "version": "1.6.0" 592 | }, 593 | { 594 | "artifacts": [ 595 | { 596 | "algorithm": "sha256", 597 | "hash": "2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", 598 | "url": "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl" 599 | }, 600 | { 601 | "algorithm": "sha256", 602 | "hash": "d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", 603 | "url": "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl" 604 | }, 605 | { 606 | "algorithm": "sha256", 607 | "hash": "2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", 608 | "url": "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl" 609 | }, 610 | { 611 | "algorithm": "sha256", 612 | "hash": "b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", 613 | "url": "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl" 614 | }, 615 | { 616 | "algorithm": "sha256", 617 | "hash": "ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", 618 | "url": "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz" 619 | } 620 | ], 621 | "project_name": "protobuf", 622 | "requires_dists": [], 623 | "requires_python": ">=3.9", 624 | "version": "6.32.1" 625 | }, 626 | { 627 | "artifacts": [ 628 | { 629 | "algorithm": "sha256", 630 | "hash": "86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", 631 | "url": "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl" 632 | }, 633 | { 634 | "algorithm": "sha256", 635 | "hash": "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", 636 | "url": "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz" 637 | } 638 | ], 639 | "project_name": "pygments", 640 | "requires_dists": [ 641 | "colorama>=0.4.6; extra == \"windows-terminal\"" 642 | ], 643 | "requires_python": ">=3.8", 644 | "version": "2.19.2" 645 | }, 646 | { 647 | "artifacts": [ 648 | { 649 | "algorithm": "sha256", 650 | "hash": "872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", 651 | "url": "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl" 652 | }, 653 | { 654 | "algorithm": "sha256", 655 | "hash": "86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", 656 | "url": "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz" 657 | } 658 | ], 659 | "project_name": "pytest", 660 | "requires_dists": [ 661 | "argcomplete; extra == \"dev\"", 662 | "attrs>=19.2; extra == \"dev\"", 663 | "colorama>=0.4; sys_platform == \"win32\"", 664 | "exceptiongroup>=1; python_version < \"3.11\"", 665 | "hypothesis>=3.56; extra == \"dev\"", 666 | "iniconfig>=1", 667 | "mock; extra == \"dev\"", 668 | "packaging>=20", 669 | "pluggy<2,>=1.5", 670 | "pygments>=2.7.2", 671 | "requests; extra == \"dev\"", 672 | "setuptools; extra == \"dev\"", 673 | "tomli>=1; python_version < \"3.11\"", 674 | "xmlschema; extra == \"dev\"" 675 | ], 676 | "requires_python": ">=3.9", 677 | "version": "8.4.2" 678 | }, 679 | { 680 | "artifacts": [ 681 | { 682 | "algorithm": "sha256", 683 | "hash": "1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", 684 | "url": "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl" 685 | }, 686 | { 687 | "algorithm": "sha256", 688 | "hash": "a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", 689 | "url": "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz" 690 | } 691 | ], 692 | "project_name": "pytest-django", 693 | "requires_dists": [ 694 | "Django; extra == \"testing\"", 695 | "django-configurations>=2.0; extra == \"testing\"", 696 | "pytest>=7.0.0", 697 | "sphinx; extra == \"docs\"", 698 | "sphinx_rtd_theme; extra == \"docs\"" 699 | ], 700 | "requires_python": ">=3.8", 701 | "version": "4.11.1" 702 | }, 703 | { 704 | "artifacts": [ 705 | { 706 | "algorithm": "sha256", 707 | "hash": "5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", 708 | "url": "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl" 709 | }, 710 | { 711 | "algorithm": "sha256", 712 | "hash": "360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", 713 | "url": "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz" 714 | } 715 | ], 716 | "project_name": "pytz", 717 | "requires_dists": [], 718 | "requires_python": null, 719 | "version": "2025.2" 720 | }, 721 | { 722 | "artifacts": [ 723 | { 724 | "algorithm": "sha256", 725 | "hash": "2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", 726 | "url": "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl" 727 | }, 728 | { 729 | "algorithm": "sha256", 730 | "hash": "dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", 731 | "url": "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz" 732 | } 733 | ], 734 | "project_name": "requests", 735 | "requires_dists": [ 736 | "PySocks!=1.5.7,>=1.5.6; extra == \"socks\"", 737 | "certifi>=2017.4.17", 738 | "chardet<6,>=3.0.2; extra == \"use-chardet-on-py3\"", 739 | "charset_normalizer<4,>=2", 740 | "idna<4,>=2.5", 741 | "urllib3<3,>=1.21.1" 742 | ], 743 | "requires_python": ">=3.9", 744 | "version": "2.32.5" 745 | }, 746 | { 747 | "artifacts": [ 748 | { 749 | "algorithm": "sha256", 750 | "hash": "062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", 751 | "url": "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl" 752 | }, 753 | { 754 | "algorithm": "sha256", 755 | "hash": "f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", 756 | "url": "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz" 757 | } 758 | ], 759 | "project_name": "setuptools", 760 | "requires_dists": [ 761 | "build[virtualenv]>=1.0.3; extra == \"test\"", 762 | "filelock>=3.4.0; extra == \"test\"", 763 | "furo; extra == \"doc\"", 764 | "importlib_metadata>=6; python_version < \"3.10\" and extra == \"core\"", 765 | "importlib_metadata>=7.0.2; python_version < \"3.10\" and extra == \"type\"", 766 | "ini2toml[lite]>=0.14; extra == \"test\"", 767 | "jaraco.develop>=7.21; (python_version >= \"3.9\" and sys_platform != \"cygwin\") and extra == \"test\"", 768 | "jaraco.develop>=7.21; sys_platform != \"cygwin\" and extra == \"type\"", 769 | "jaraco.envs>=2.2; extra == \"test\"", 770 | "jaraco.functools>=4; extra == \"core\"", 771 | "jaraco.packaging>=9.3; extra == \"doc\"", 772 | "jaraco.path>=3.7.2; extra == \"test\"", 773 | "jaraco.test>=5.5; extra == \"test\"", 774 | "jaraco.text>=3.7; extra == \"core\"", 775 | "jaraco.tidelift>=1.4; extra == \"doc\"", 776 | "more_itertools; extra == \"core\"", 777 | "more_itertools>=8.8; extra == \"core\"", 778 | "mypy==1.14.*; extra == \"type\"", 779 | "packaging>=24.2; extra == \"core\"", 780 | "packaging>=24.2; extra == \"test\"", 781 | "pip>=19.1; extra == \"test\"", 782 | "platformdirs>=4.2.2; extra == \"core\"", 783 | "pygments-github-lexers==0.0.5; extra == \"doc\"", 784 | "pyproject-hooks!=1.1; extra == \"doc\"", 785 | "pyproject-hooks!=1.1; extra == \"test\"", 786 | "pytest!=8.1.*,>=6; extra == \"test\"", 787 | "pytest-checkdocs>=2.4; extra == \"check\"", 788 | "pytest-cov; extra == \"cover\"", 789 | "pytest-enabler>=2.2; extra == \"enabler\"", 790 | "pytest-home>=0.5; extra == \"test\"", 791 | "pytest-mypy; extra == \"type\"", 792 | "pytest-perf; sys_platform != \"cygwin\" and extra == \"test\"", 793 | "pytest-ruff>=0.2.1; sys_platform != \"cygwin\" and extra == \"check\"", 794 | "pytest-subprocess; extra == \"test\"", 795 | "pytest-timeout; extra == \"test\"", 796 | "pytest-xdist>=3; extra == \"test\"", 797 | "rst.linker>=1.9; extra == \"doc\"", 798 | "ruff>=0.8.0; sys_platform != \"cygwin\" and extra == \"check\"", 799 | "sphinx-favicon; extra == \"doc\"", 800 | "sphinx-inline-tabs; extra == \"doc\"", 801 | "sphinx-lint; extra == \"doc\"", 802 | "sphinx-notfound-page<2,>=1; extra == \"doc\"", 803 | "sphinx-reredirects; extra == \"doc\"", 804 | "sphinx>=3.5; extra == \"doc\"", 805 | "sphinxcontrib-towncrier; extra == \"doc\"", 806 | "tomli-w>=1.0.0; extra == \"test\"", 807 | "tomli>=2.0.1; python_version < \"3.11\" and extra == \"core\"", 808 | "towncrier<24.7; extra == \"doc\"", 809 | "virtualenv>=13.0.0; extra == \"test\"", 810 | "wheel>=0.43.0; extra == \"core\"", 811 | "wheel>=0.44.0; extra == \"test\"" 812 | ], 813 | "requires_python": ">=3.9", 814 | "version": "80.9.0" 815 | }, 816 | { 817 | "artifacts": [ 818 | { 819 | "algorithm": "sha256", 820 | "hash": "cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", 821 | "url": "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl" 822 | }, 823 | { 824 | "algorithm": "sha256", 825 | "hash": "09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", 826 | "url": "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz" 827 | } 828 | ], 829 | "project_name": "sqlparse", 830 | "requires_dists": [ 831 | "build; extra == \"dev\"", 832 | "hatch; extra == \"dev\"", 833 | "sphinx; extra == \"doc\"" 834 | ], 835 | "requires_python": ">=3.8", 836 | "version": "0.5.3" 837 | }, 838 | { 839 | "artifacts": [ 840 | { 841 | "algorithm": "sha256", 842 | "hash": "cebfb004989d9a2ab0d24c0c5805783c7f4e07243ea4ed2a8f1809d072bf712b", 843 | "url": "https://files.pythonhosted.org/packages/e1/54/df0d3e62636f291d716caa44c5c9590af8e6f225c59d7874051da6428c06/translate-3.6.1-py2.py3-none-any.whl" 844 | }, 845 | { 846 | "algorithm": "sha256", 847 | "hash": "7e70ffa46f193cc744be7c88b8e1323f10f6b2bb90d24bb5d29fdf1e56618783", 848 | "url": "https://files.pythonhosted.org/packages/d1/9d/692066b9e26176a93ce627bf467bca48f198eb0e036337a5180d566c9561/translate-3.6.1.tar.gz" 849 | } 850 | ], 851 | "project_name": "translate", 852 | "requires_dists": [ 853 | "click", 854 | "libretranslatepy==2.1.1", 855 | "lxml", 856 | "requests" 857 | ], 858 | "requires_python": null, 859 | "version": "3.6.1" 860 | }, 861 | { 862 | "artifacts": [ 863 | { 864 | "algorithm": "sha256", 865 | "hash": "4f55ed1b43e925cf851a756fe1707e0f5deeb1976e15bf844bcaa025e8fbd0db", 866 | "url": "https://files.pythonhosted.org/packages/db/d0/91c24fe54e565f2344d7a6821e6c6bb099841ef09007ea6321a0bac0f808/types_pytz-2025.2.0.20250809-py3-none-any.whl" 867 | }, 868 | { 869 | "algorithm": "sha256", 870 | "hash": "222e32e6a29bb28871f8834e8785e3801f2dc4441c715cd2082b271eecbe21e5", 871 | "url": "https://files.pythonhosted.org/packages/07/e2/c774f754de26848f53f05defff5bb21dd9375a059d1ba5b5ea943cf8206e/types_pytz-2025.2.0.20250809.tar.gz" 872 | } 873 | ], 874 | "project_name": "types-pytz", 875 | "requires_dists": [], 876 | "requires_python": ">=3.9", 877 | "version": "2025.2.0.20250809" 878 | }, 879 | { 880 | "artifacts": [ 881 | { 882 | "algorithm": "sha256", 883 | "hash": "e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", 884 | "url": "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl" 885 | }, 886 | { 887 | "algorithm": "sha256", 888 | "hash": "0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", 889 | "url": "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz" 890 | } 891 | ], 892 | "project_name": "types-pyyaml", 893 | "requires_dists": [], 894 | "requires_python": ">=3.9", 895 | "version": "6.0.12.20250915" 896 | }, 897 | { 898 | "artifacts": [ 899 | { 900 | "algorithm": "sha256", 901 | "hash": "78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", 902 | "url": "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl" 903 | }, 904 | { 905 | "algorithm": "sha256", 906 | "hash": "abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", 907 | "url": "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz" 908 | } 909 | ], 910 | "project_name": "types-requests", 911 | "requires_dists": [ 912 | "urllib3>=2" 913 | ], 914 | "requires_python": ">=3.9", 915 | "version": "2.32.4.20250913" 916 | }, 917 | { 918 | "artifacts": [ 919 | { 920 | "algorithm": "sha256", 921 | "hash": "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", 922 | "url": "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl" 923 | }, 924 | { 925 | "algorithm": "sha256", 926 | "hash": "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", 927 | "url": "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz" 928 | } 929 | ], 930 | "project_name": "typing-extensions", 931 | "requires_dists": [], 932 | "requires_python": ">=3.9", 933 | "version": "4.15.0" 934 | }, 935 | { 936 | "artifacts": [ 937 | { 938 | "algorithm": "sha256", 939 | "hash": "e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", 940 | "url": "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl" 941 | }, 942 | { 943 | "algorithm": "sha256", 944 | "hash": "3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", 945 | "url": "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz" 946 | } 947 | ], 948 | "project_name": "urllib3", 949 | "requires_dists": [ 950 | "brotli>=1.0.9; platform_python_implementation == \"CPython\" and extra == \"brotli\"", 951 | "brotlicffi>=0.8.0; platform_python_implementation != \"CPython\" and extra == \"brotli\"", 952 | "h2<5,>=4; extra == \"h2\"", 953 | "pysocks!=1.5.7,<2.0,>=1.5.6; extra == \"socks\"", 954 | "zstandard>=0.18.0; extra == \"zstd\"" 955 | ], 956 | "requires_python": ">=3.9", 957 | "version": "2.5.0" 958 | } 959 | ], 960 | "platform_tag": null 961 | } 962 | ], 963 | "only_builds": [], 964 | "only_wheels": [], 965 | "overridden": [], 966 | "path_mappings": {}, 967 | "pex_version": "2.55.2", 968 | "pip_version": "24.2", 969 | "prefer_older_binary": false, 970 | "requirements": [ 971 | "ansicolors>=1.0.2", 972 | "django-debug-toolbar<4,>=3.8.0", 973 | "django-stubs~=4.2.7", 974 | "django<4,>=3.2.13", 975 | "gunicorn>=20.1.0", 976 | "mypy==1.18.1", 977 | "protobuf>=3.11.3", 978 | "pytest-django<5,>=4", 979 | "pytest>=6.0.1", 980 | "requests>=2.25.1", 981 | "setuptools>=42.0.0", 982 | "translate>=3.2.1", 983 | "types-requests>=2.25.1" 984 | ], 985 | "requires_python": [ 986 | "CPython<3.13,>=3.12" 987 | ], 988 | "resolver_version": "pip-2020-resolver", 989 | "style": "universal", 990 | "target_systems": [ 991 | "linux", 992 | "mac" 993 | ], 994 | "transitive": true, 995 | "use_pep517": null, 996 | "use_system_time": false 997 | } 998 | --------------------------------------------------------------------------------