├── .flake8 ├── .github └── workflows │ └── pants.yaml ├── .gitignore ├── .isort.cfg ├── BUILD ├── BUILDROOT ├── LICENSE ├── README.md ├── build-support └── generate_constraints.sh ├── helloworld ├── BUILD ├── __init__.py ├── greet │ ├── BUILD │ ├── __init__.py │ ├── admin.py │ ├── conftest.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_data.py │ │ ├── BUILD │ │ └── __init__.py │ ├── models.py │ ├── models_test.py │ ├── urls.py │ └── views.py ├── gunicorn_conf.py ├── person │ ├── BUILD │ ├── __init__.py │ ├── admin.py │ ├── conftest.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_data.py │ │ ├── BUILD │ │ └── __init__.py │ ├── models.py │ ├── models_test.py │ ├── urls.py │ └── views.py ├── service │ ├── __init__.py │ ├── admin │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── gunicorn.py │ │ ├── manage.py │ │ ├── settings.py │ │ └── urls.py │ ├── frontend │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── gunicorn.py │ │ ├── manage.py │ │ ├── settings.py │ │ └── urls.py │ ├── user │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── gunicorn.py │ │ ├── manage.py │ │ ├── settings.py │ │ └── urls.py │ └── welcome │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── gunicorn.py │ │ ├── manage.py │ │ ├── settings.py │ │ └── urls.py ├── settings_base.py ├── translate │ ├── BUILD │ ├── __init__.py │ ├── admin.py │ ├── conftest.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_data.py │ │ ├── BUILD │ │ └── __init__.py │ ├── models.py │ ├── models_test.py │ ├── urls.py │ └── views.py ├── ui │ ├── BUILD │ ├── __init__.py │ ├── static │ │ ├── BUILD │ │ └── helloworld │ │ │ └── ui │ │ │ └── helloworld.css │ ├── templates │ │ ├── BUILD │ │ └── helloworld │ │ │ └── ui │ │ │ └── index.html │ ├── urls.py │ └── views.py ├── util │ ├── BUILD │ ├── __init__.py │ ├── backend.py │ ├── discovery.py │ ├── mode.py │ ├── per_app_db_router.py │ ├── service.py │ └── settings_for_tests.py └── wsgi.py ├── lockfiles └── python-default.lock ├── mypy.ini ├── pants.ci.toml ├── pants.toml ├── pytest.ini └── requirements.txt /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore: 3 | E203, # whitespace before ':' (conflicts with Black) 4 | E231, # Bad trailing comma (conflicts with Black) 5 | E501, # line too long (conflicts with Black) 6 | -------------------------------------------------------------------------------- /.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.9] 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /BUILDROOT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/BUILDROOT -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build-support/generate_constraints.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). 3 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 4 | 5 | # See https://www.pantsbuild.org/v2.0/docs/python-third-party-dependencies. 6 | 7 | set -euo pipefail 8 | 9 | PYTHON_BIN=python3 10 | VIRTUALENV=build-support/.venv 11 | PIP="${VIRTUALENV}/bin/pip" 12 | CONSTRAINTS_FILE=constraints.txt 13 | 14 | "${PYTHON_BIN}" -m venv "${VIRTUALENV}" 15 | "${PIP}" install pip --upgrade 16 | "${PIP}" install -r <(pants dependencies --type=3rdparty ::) -r requirements.txt 17 | echo "# Generated by build-support/generate_constraints.sh on $(date)" > "${CONSTRAINTS_FILE}" 18 | "${PIP}" freeze --all >> "${CONSTRAINTS_FILE}" 19 | -------------------------------------------------------------------------------- /helloworld/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors. 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | python_sources() 5 | -------------------------------------------------------------------------------- /helloworld/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/__init__.py -------------------------------------------------------------------------------- /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/greet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/greet/__init__.py -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/greet/migrations/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /helloworld/greet/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/greet/migrations/__init__.py -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/person/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/person/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /helloworld/person/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/person/migrations/__init__.py -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /helloworld/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/service/__init__.py -------------------------------------------------------------------------------- /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/service/admin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/service/admin/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/service/frontend/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/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/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/service/user/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/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/welcome/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/service/welcome/__init__.py -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/translate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/translate/__init__.py -------------------------------------------------------------------------------- /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/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/translate/migrations/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /helloworld/translate/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/translate/migrations/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/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/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/ui/__init__.py -------------------------------------------------------------------------------- /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/static/helloworld/ui/helloworld.css: -------------------------------------------------------------------------------- 1 | 2 | .greeting { 3 | color: blue; 4 | } 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 | -------------------------------------------------------------------------------- /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/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/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/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/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantsbuild/example-django/cb215c3688bf8cb3f2be46af95ae181ff341b9da/helloworld/util/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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": 3, 8 | // "valid_for_interpreter_constraints": [ 9 | // "CPython<3.10,>=3.9" 10 | // ], 11 | // "generated_with_requirements": [ 12 | // "ansicolors>=1.0.2", 13 | // "django-debug-toolbar<4,>=3.8.0", 14 | // "django-stubs==1.10.1", 15 | // "django<4,>=3.2.13", 16 | // "gunicorn>=20.1.0", 17 | // "mypy==0.942", 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 | // } 31 | // --- END PANTS LOCKFILE METADATA --- 32 | 33 | { 34 | "allow_builds": true, 35 | "allow_prereleases": false, 36 | "allow_wheels": true, 37 | "build_isolation": true, 38 | "constraints": [], 39 | "locked_resolves": [ 40 | { 41 | "locked_requirements": [ 42 | { 43 | "artifacts": [ 44 | { 45 | "algorithm": "sha256", 46 | "hash": "00d2dde5a675579325902536738dd27e4fac1fd68f773fe36c21044eb559e187", 47 | "url": "https://files.pythonhosted.org/packages/53/18/a56e2fe47b259bb52201093a3a9d4a32014f9d85071ad07e9d60600890ca/ansicolors-1.1.8-py2.py3-none-any.whl" 48 | }, 49 | { 50 | "algorithm": "sha256", 51 | "hash": "99f94f5e3348a0bcd43c82e5fc4414013ccc19d70bd939ad71e0133ce9c372e0", 52 | "url": "https://files.pythonhosted.org/packages/76/31/7faed52088732704523c259e24c26ce6f2f33fbeff2ff59274560c27628e/ansicolors-1.1.8.zip" 53 | } 54 | ], 55 | "project_name": "ansicolors", 56 | "requires_dists": [], 57 | "requires_python": null, 58 | "version": "1.1.8" 59 | }, 60 | { 61 | "artifacts": [ 62 | { 63 | "algorithm": "sha256", 64 | "hash": "89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", 65 | "url": "https://files.pythonhosted.org/packages/9b/80/b9051a4a07ad231558fcd8ffc89232711b4e618c15cb7a392a17384bbeef/asgiref-3.7.2-py3-none-any.whl" 66 | }, 67 | { 68 | "algorithm": "sha256", 69 | "hash": "9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed", 70 | "url": "https://files.pythonhosted.org/packages/12/19/64e38c1c2cbf0da9635b7082bbdf0e89052e93329279f59759c24a10cc96/asgiref-3.7.2.tar.gz" 71 | } 72 | ], 73 | "project_name": "asgiref", 74 | "requires_dists": [ 75 | "mypy>=0.800; extra == \"tests\"", 76 | "pytest-asyncio; extra == \"tests\"", 77 | "pytest; extra == \"tests\"", 78 | "typing-extensions>=4; python_version < \"3.11\"" 79 | ], 80 | "requires_python": ">=3.7", 81 | "version": "3.7.2" 82 | }, 83 | { 84 | "artifacts": [ 85 | { 86 | "algorithm": "sha256", 87 | "hash": "92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9", 88 | "url": "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl" 89 | }, 90 | { 91 | "algorithm": "sha256", 92 | "hash": "539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", 93 | "url": "https://files.pythonhosted.org/packages/98/98/c2ff18671db109c9f10ed27f5ef610ae05b73bd876664139cf95bd1429aa/certifi-2023.7.22.tar.gz" 94 | } 95 | ], 96 | "project_name": "certifi", 97 | "requires_dists": [], 98 | "requires_python": ">=3.6", 99 | "version": "2023.7.22" 100 | }, 101 | { 102 | "artifacts": [ 103 | { 104 | "algorithm": "sha256", 105 | "hash": "8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", 106 | "url": "https://files.pythonhosted.org/packages/bf/a0/188f223c7d8b924fb9b554b9d27e0e7506fd5bf9cfb6dbacb2dfd5832b53/charset_normalizer-3.2.0-py3-none-any.whl" 107 | }, 108 | { 109 | "algorithm": "sha256", 110 | "hash": "855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", 111 | "url": "https://files.pythonhosted.org/packages/09/79/1b7af063e7c57a51aab7f2aaccd79bb8a694dfae668e8aa79b0b045b17bc/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl" 112 | }, 113 | { 114 | "algorithm": "sha256", 115 | "hash": "c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", 116 | "url": "https://files.pythonhosted.org/packages/1b/2c/7376d101efdec15e61e9861890cf107c6ce3cceba89eb87cc416ee0528cd/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl" 117 | }, 118 | { 119 | "algorithm": "sha256", 120 | "hash": "1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", 121 | "url": "https://files.pythonhosted.org/packages/23/59/8011a01cd8b904d08d86b4a49f407e713d20ee34155300dc698892a29f8b/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" 122 | }, 123 | { 124 | "algorithm": "sha256", 125 | "hash": "3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", 126 | "url": "https://files.pythonhosted.org/packages/2a/53/cf0a48de1bdcf6ff6e1c9a023f5f523dfe303e4024f216feac64b6eb7f67/charset-normalizer-3.2.0.tar.gz" 127 | }, 128 | { 129 | "algorithm": "sha256", 130 | "hash": "5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", 131 | "url": "https://files.pythonhosted.org/packages/47/03/2cde6c5fba0115e8726272aabfca33b9d84d377cc11c4bab092fa9617d7a/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" 132 | }, 133 | { 134 | "algorithm": "sha256", 135 | "hash": "c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", 136 | "url": "https://files.pythonhosted.org/packages/4a/46/a22af93e707f0d3c3865a2c21b4363c778239f5a6405aadd220992ac3058/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" 137 | }, 138 | { 139 | "algorithm": "sha256", 140 | "hash": "203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", 141 | "url": "https://files.pythonhosted.org/packages/7b/c6/7f75892d87d7afcf8ed909f3e74de1bc61abd9d77cd9aab1f449430856c5/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl" 142 | }, 143 | { 144 | "algorithm": "sha256", 145 | "hash": "2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", 146 | "url": "https://files.pythonhosted.org/packages/80/75/eadff07a61d5602b6b19859d464bc0983654ae79114ef8aa15797b02271c/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" 147 | }, 148 | { 149 | "algorithm": "sha256", 150 | "hash": "c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", 151 | "url": "https://files.pythonhosted.org/packages/85/52/77ab28e0eb07f12a02732c55abfc3be481bd46c91d5ade76a8904dfb59a4/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl" 152 | }, 153 | { 154 | "algorithm": "sha256", 155 | "hash": "cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", 156 | "url": "https://files.pythonhosted.org/packages/95/d3/ed29b2d14ec9044a223dcf7c439fa550ef9c6d06c9372cd332374d990559/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl" 157 | }, 158 | { 159 | "algorithm": "sha256", 160 | "hash": "e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", 161 | "url": "https://files.pythonhosted.org/packages/d3/d8/50a33f82bdf25e71222a55cef146310e3e9fe7d5790be5281d715c012eae/charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl" 162 | }, 163 | { 164 | "algorithm": "sha256", 165 | "hash": "f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", 166 | "url": "https://files.pythonhosted.org/packages/ed/21/03b4a3533b7a845ee31ed4542ca06debdcf7f12c099ae3dd6773c275b0df/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl" 167 | }, 168 | { 169 | "algorithm": "sha256", 170 | "hash": "1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", 171 | "url": "https://files.pythonhosted.org/packages/f2/e8/d9651a0afd4ee792207b24bd1d438ed750f1c0f29df62bd73d24ded428f9/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl" 172 | }, 173 | { 174 | "algorithm": "sha256", 175 | "hash": "8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", 176 | "url": "https://files.pythonhosted.org/packages/f9/0d/514be8597d7a96243e5467a37d337b9399cec117a513fcf9328405d911c0/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" 177 | } 178 | ], 179 | "project_name": "charset-normalizer", 180 | "requires_dists": [], 181 | "requires_python": ">=3.7.0", 182 | "version": "3.2.0" 183 | }, 184 | { 185 | "artifacts": [ 186 | { 187 | "algorithm": "sha256", 188 | "hash": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", 189 | "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl" 190 | }, 191 | { 192 | "algorithm": "sha256", 193 | "hash": "ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", 194 | "url": "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz" 195 | } 196 | ], 197 | "project_name": "click", 198 | "requires_dists": [ 199 | "colorama; platform_system == \"Windows\"", 200 | "importlib-metadata; python_version < \"3.8\"" 201 | ], 202 | "requires_python": ">=3.7", 203 | "version": "8.1.7" 204 | }, 205 | { 206 | "artifacts": [ 207 | { 208 | "algorithm": "sha256", 209 | "hash": "d31b06c58aa2cd73998ca5966bc3001243d3c4e77ee2d0c479bced124765fd99", 210 | "url": "https://files.pythonhosted.org/packages/97/8d/03ac2f7a3f751f597be12c03a09147f9ca80906264044bb05758bbcc2b32/Django-3.2.21-py3-none-any.whl" 211 | }, 212 | { 213 | "algorithm": "sha256", 214 | "hash": "a5de4c484e7b7418e6d3e52a5b8794f0e6b9f9e4ce3c037018cf1c489fa87f3c", 215 | "url": "https://files.pythonhosted.org/packages/9f/4a/2b57890fd9a1511ed3c580ef3636f018d06b1f9ae42b3033e4fff3ff7720/Django-3.2.21.tar.gz" 216 | } 217 | ], 218 | "project_name": "django", 219 | "requires_dists": [ 220 | "argon2-cffi>=19.1.0; extra == \"argon2\"", 221 | "asgiref<4,>=3.3.2", 222 | "bcrypt; extra == \"bcrypt\"", 223 | "pytz", 224 | "sqlparse>=0.2.2" 225 | ], 226 | "requires_python": ">=3.6", 227 | "version": "3.2.21" 228 | }, 229 | { 230 | "artifacts": [ 231 | { 232 | "algorithm": "sha256", 233 | "hash": "879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478", 234 | "url": "https://files.pythonhosted.org/packages/c7/c3/fd81e84ce86904019112428c690a06ab19c66742c917a898dcf9da6ae166/django_debug_toolbar-3.8.1-py3-none-any.whl" 235 | }, 236 | { 237 | "algorithm": "sha256", 238 | "hash": "24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27", 239 | "url": "https://files.pythonhosted.org/packages/80/53/1d1e3bbef3c6f5820f838a6a6a8e795a3c86323f9dbf2f8a89de78477972/django_debug_toolbar-3.8.1.tar.gz" 240 | } 241 | ], 242 | "project_name": "django-debug-toolbar", 243 | "requires_dists": [ 244 | "django>=3.2.4", 245 | "sqlparse>=0.2" 246 | ], 247 | "requires_python": ">=3.7", 248 | "version": "3.8.1" 249 | }, 250 | { 251 | "artifacts": [ 252 | { 253 | "algorithm": "sha256", 254 | "hash": "dfffa933e3c7f5cae92c860fbd6cf4ae58c4f78eed93843612f863207c9e0e1a", 255 | "url": "https://files.pythonhosted.org/packages/9b/70/fa9c13b2c9305f5728ee7200e4666a0f66472171bb4656919fc7ff3be35e/django_stubs-1.10.1-py3-none-any.whl" 256 | }, 257 | { 258 | "algorithm": "sha256", 259 | "hash": "2ec21fc14dba392156e0ec8438e1863c86ddb295f1c8d88eecd7e0e04977c843", 260 | "url": "https://files.pythonhosted.org/packages/20/b7/07950c67ab79042c52fc1a2b32c5c6995385bd1a5333206b88a6e8380476/django-stubs-1.10.1.tar.gz" 261 | } 262 | ], 263 | "project_name": "django-stubs", 264 | "requires_dists": [ 265 | "django", 266 | "django-stubs-ext>=0.4.0", 267 | "mypy<0.950,>=0.930", 268 | "tomli", 269 | "types-PyYAML", 270 | "types-pytz", 271 | "typing-extensions" 272 | ], 273 | "requires_python": ">=3.7", 274 | "version": "1.10.1" 275 | }, 276 | { 277 | "artifacts": [ 278 | { 279 | "algorithm": "sha256", 280 | "hash": "fdacc65a14d2d4b97334b58ff178a5853ec8c8c76cec406e417916ad67536ce4", 281 | "url": "https://files.pythonhosted.org/packages/92/e5/f186498e66e7cf505e8e4cf2bb9fa631bdf61a33dbb997558f4fdbcc6240/django_stubs_ext-4.2.2-py3-none-any.whl" 282 | }, 283 | { 284 | "algorithm": "sha256", 285 | "hash": "c69d1cc46f1c4c3b7894b685a5022c29b2a36c7cfb52e23762eaf357ebfc2c98", 286 | "url": "https://files.pythonhosted.org/packages/58/31/2950163649bb058b164227a6bfd015d3b2b24c8a07463ba1f996d9f15e03/django-stubs-ext-4.2.2.tar.gz" 287 | } 288 | ], 289 | "project_name": "django-stubs-ext", 290 | "requires_dists": [ 291 | "django", 292 | "typing-extensions" 293 | ], 294 | "requires_python": ">=3.8", 295 | "version": "4.2.2" 296 | }, 297 | { 298 | "artifacts": [ 299 | { 300 | "algorithm": "sha256", 301 | "hash": "343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3", 302 | "url": "https://files.pythonhosted.org/packages/ad/83/b71e58666f156a39fb29417e4c8ca4bc7400c0dd4ed9e8842ab54dc8c344/exceptiongroup-1.1.3-py3-none-any.whl" 303 | }, 304 | { 305 | "algorithm": "sha256", 306 | "hash": "097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9", 307 | "url": "https://files.pythonhosted.org/packages/c2/e1/5561ad26f99b7779c28356f73f69a8b468ef491d0f6adf20d7ed0ac98ec1/exceptiongroup-1.1.3.tar.gz" 308 | } 309 | ], 310 | "project_name": "exceptiongroup", 311 | "requires_dists": [ 312 | "pytest>=6; extra == \"test\"" 313 | ], 314 | "requires_python": ">=3.7", 315 | "version": "1.1.3" 316 | }, 317 | { 318 | "artifacts": [ 319 | { 320 | "algorithm": "sha256", 321 | "hash": "3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", 322 | "url": "https://files.pythonhosted.org/packages/0e/2a/c3a878eccb100ccddf45c50b6b8db8cf3301a6adede6e31d48e8531cab13/gunicorn-21.2.0-py3-none-any.whl" 323 | }, 324 | { 325 | "algorithm": "sha256", 326 | "hash": "88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033", 327 | "url": "https://files.pythonhosted.org/packages/06/89/acd9879fa6a5309b4bf16a5a8855f1e58f26d38e0c18ede9b3a70996b021/gunicorn-21.2.0.tar.gz" 328 | } 329 | ], 330 | "project_name": "gunicorn", 331 | "requires_dists": [ 332 | "eventlet>=0.24.1; extra == \"eventlet\"", 333 | "gevent>=1.4.0; extra == \"gevent\"", 334 | "importlib-metadata; python_version < \"3.8\"", 335 | "packaging", 336 | "setproctitle; extra == \"setproctitle\"", 337 | "tornado>=0.2; extra == \"tornado\"" 338 | ], 339 | "requires_python": ">=3.5", 340 | "version": "21.2.0" 341 | }, 342 | { 343 | "artifacts": [ 344 | { 345 | "algorithm": "sha256", 346 | "hash": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2", 347 | "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl" 348 | }, 349 | { 350 | "algorithm": "sha256", 351 | "hash": "814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 352 | "url": "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz" 353 | } 354 | ], 355 | "project_name": "idna", 356 | "requires_dists": [], 357 | "requires_python": ">=3.5", 358 | "version": "3.4" 359 | }, 360 | { 361 | "artifacts": [ 362 | { 363 | "algorithm": "sha256", 364 | "hash": "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", 365 | "url": "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" 366 | }, 367 | { 368 | "algorithm": "sha256", 369 | "hash": "2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", 370 | "url": "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz" 371 | } 372 | ], 373 | "project_name": "iniconfig", 374 | "requires_dists": [], 375 | "requires_python": ">=3.7", 376 | "version": "2.0.0" 377 | }, 378 | { 379 | "artifacts": [ 380 | { 381 | "algorithm": "sha256", 382 | "hash": "bf9f8b0003c94f34e141553b7ec0a02876297c06955f5efea49afbc9f85303a9", 383 | "url": "https://files.pythonhosted.org/packages/24/54/044ecbf4eeba3f3feee70d496f34f397dc20714cdb80dfa8bc01e7409f88/libretranslatepy-2.1.1-py3-none-any.whl" 384 | }, 385 | { 386 | "algorithm": "sha256", 387 | "hash": "3f28e1b990ba5f514ae215c08ace0c4e2327eeccaa356983aefbca3a25ecc568", 388 | "url": "https://files.pythonhosted.org/packages/ef/ec/9b31f896697e164204ac4a31c9cfc31c0fee97ca5b97829d24a9cfc7d33c/libretranslatepy-2.1.1.tar.gz" 389 | } 390 | ], 391 | "project_name": "libretranslatepy", 392 | "requires_dists": [], 393 | "requires_python": null, 394 | "version": "2.1.1" 395 | }, 396 | { 397 | "artifacts": [ 398 | { 399 | "algorithm": "sha256", 400 | "hash": "fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4", 401 | "url": "https://files.pythonhosted.org/packages/1b/ea/50d8357ed72f6c8352fe08657a9b05d672a4ab2470b9447fd73d87f0d47a/lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl" 402 | }, 403 | { 404 | "algorithm": "sha256", 405 | "hash": "48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c", 406 | "url": "https://files.pythonhosted.org/packages/30/39/7305428d1c4f28282a4f5bdbef24e0f905d351f34cf351ceb131f5cddf78/lxml-4.9.3.tar.gz" 407 | }, 408 | { 409 | "algorithm": "sha256", 410 | "hash": "690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d", 411 | "url": "https://files.pythonhosted.org/packages/35/0b/7c3b67cf80d3b826273f860c20810c791c39ce55df612c8bf4bbdfa1ec11/lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl" 412 | }, 413 | { 414 | "algorithm": "sha256", 415 | "hash": "d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50", 416 | "url": "https://files.pythonhosted.org/packages/39/6f/ed4327ac1370da702b7ac4047f4664fbdfb92a98b87ac779863202efc1ff/lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl" 417 | }, 418 | { 419 | "algorithm": "sha256", 420 | "hash": "9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340", 421 | "url": "https://files.pythonhosted.org/packages/50/e1/1c23a817d68418a59b39e6d2b353e211728c21353900279a04e65f6507a0/lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl" 422 | }, 423 | { 424 | "algorithm": "sha256", 425 | "hash": "b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432", 426 | "url": "https://files.pythonhosted.org/packages/6a/bf/a2f75c3c1e4e310e14cfe81117c8c1fca092bbb595babebd7df66b8eb5e6/lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl" 427 | }, 428 | { 429 | "algorithm": "sha256", 430 | "hash": "8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7", 431 | "url": "https://files.pythonhosted.org/packages/6d/8f/eb30dead5cd2dd8f0a91f8bcb371688e158c8cbb4a3496e41196ea1171c0/lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl" 432 | }, 433 | { 434 | "algorithm": "sha256", 435 | "hash": "bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69", 436 | "url": "https://files.pythonhosted.org/packages/a2/e5/78402bb7bc3e9b43c7857f9e2c60ab5b5d5d9aa35ae76b4043b12535eb18/lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl" 437 | }, 438 | { 439 | "algorithm": "sha256", 440 | "hash": "ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0", 441 | "url": "https://files.pythonhosted.org/packages/b6/4b/8964ca1238c6952d33afdcd89771a95a7e4ac7949e543c6685a6bd7b47c2/lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl" 442 | }, 443 | { 444 | "algorithm": "sha256", 445 | "hash": "5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e", 446 | "url": "https://files.pythonhosted.org/packages/c5/a2/7876f76606725340c989b1c73b5501fc41fb21e50a8597c9ecdb63a05b27/lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl" 447 | }, 448 | { 449 | "algorithm": "sha256", 450 | "hash": "fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b", 451 | "url": "https://files.pythonhosted.org/packages/cc/b9/d822b2fc9b9406cff3ec6be03d69adb0e44fcad09608489ea4acaf2443bb/lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" 452 | }, 453 | { 454 | "algorithm": "sha256", 455 | "hash": "303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da", 456 | "url": "https://files.pythonhosted.org/packages/de/07/fcfc5adfb793e6181049a361b2909723c5821b2bc18a21064da4429631a8/lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl" 457 | }, 458 | { 459 | "algorithm": "sha256", 460 | "hash": "8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694", 461 | "url": "https://files.pythonhosted.org/packages/e4/f0/715c36f1fa3a12c0df47aef20b2ddcb09dbb346c7861628bbccf4a7cb8f4/lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl" 462 | }, 463 | { 464 | "algorithm": "sha256", 465 | "hash": "e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7", 466 | "url": "https://files.pythonhosted.org/packages/e5/47/fab85a1b473be9fc2a3b3329e0970b287123bd02367fe83ae06ca6ef5f58/lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" 467 | } 468 | ], 469 | "project_name": "lxml", 470 | "requires_dists": [ 471 | "BeautifulSoup4; extra == \"htmlsoup\"", 472 | "Cython>=0.29.35; extra == \"source\"", 473 | "cssselect>=0.7; extra == \"cssselect\"", 474 | "html5lib; extra == \"html5\"" 475 | ], 476 | "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", 477 | "version": "4.9.3" 478 | }, 479 | { 480 | "artifacts": [ 481 | { 482 | "algorithm": "sha256", 483 | "hash": "a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db", 484 | "url": "https://files.pythonhosted.org/packages/dd/c1/072c3187d19d1e5445a0230b526916e37497eefe6b060491845603fa6f95/mypy-0.942-py3-none-any.whl" 485 | }, 486 | { 487 | "algorithm": "sha256", 488 | "hash": "6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca", 489 | "url": "https://files.pythonhosted.org/packages/52/67/f3bfbb3b73c8b154a7fe2c4e36c24751329a67151a014dc7b12a6e45e386/mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl" 490 | }, 491 | { 492 | "algorithm": "sha256", 493 | "hash": "10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16", 494 | "url": "https://files.pythonhosted.org/packages/67/13/e99a8d4f804f3db73073512f04cce0fad7d0c75cc69245f99eab9ebf67d9/mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl" 495 | }, 496 | { 497 | "algorithm": "sha256", 498 | "hash": "3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6", 499 | "url": "https://files.pythonhosted.org/packages/7f/90/27f32c7c4cac281b7e0a2bdf225f0e3020dc091efcba4bc2efc32d1d5592/mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl" 500 | }, 501 | { 502 | "algorithm": "sha256", 503 | "hash": "6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322", 504 | "url": "https://files.pythonhosted.org/packages/84/c7/9555abe67e9d1b5300042797f5c2829de40c831e5d99a8f467e741a4816a/mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl" 505 | }, 506 | { 507 | "algorithm": "sha256", 508 | "hash": "17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2", 509 | "url": "https://files.pythonhosted.org/packages/e3/7b/3fa271e904b0e8689fb0f0083a43a8bf84b9b47b8f3f5a36e0ee2c064eb4/mypy-0.942.tar.gz" 510 | } 511 | ], 512 | "project_name": "mypy", 513 | "requires_dists": [ 514 | "lxml; extra == \"reports\"", 515 | "mypy-extensions>=0.4.3", 516 | "psutil>=4.0; extra == \"dmypy\"", 517 | "tomli>=1.1.0", 518 | "typed-ast<2,>=1.4.0; extra == \"python2\"", 519 | "typed-ast<2,>=1.4.0; python_version < \"3.8\"", 520 | "typing-extensions>=3.10" 521 | ], 522 | "requires_python": ">=3.6", 523 | "version": "0.942" 524 | }, 525 | { 526 | "artifacts": [ 527 | { 528 | "algorithm": "sha256", 529 | "hash": "4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", 530 | "url": "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl" 531 | }, 532 | { 533 | "algorithm": "sha256", 534 | "hash": "75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", 535 | "url": "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz" 536 | } 537 | ], 538 | "project_name": "mypy-extensions", 539 | "requires_dists": [], 540 | "requires_python": ">=3.5", 541 | "version": "1.0.0" 542 | }, 543 | { 544 | "artifacts": [ 545 | { 546 | "algorithm": "sha256", 547 | "hash": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", 548 | "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl" 549 | }, 550 | { 551 | "algorithm": "sha256", 552 | "hash": "a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f", 553 | "url": "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz" 554 | } 555 | ], 556 | "project_name": "packaging", 557 | "requires_dists": [], 558 | "requires_python": ">=3.7", 559 | "version": "23.1" 560 | }, 561 | { 562 | "artifacts": [ 563 | { 564 | "algorithm": "sha256", 565 | "hash": "d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7", 566 | "url": "https://files.pythonhosted.org/packages/05/b8/42ed91898d4784546c5f06c60506400548db3f7a4b3fb441cba4e5c17952/pluggy-1.3.0-py3-none-any.whl" 567 | }, 568 | { 569 | "algorithm": "sha256", 570 | "hash": "cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", 571 | "url": "https://files.pythonhosted.org/packages/36/51/04defc761583568cae5fd533abda3d40164cbdcf22dee5b7126ffef68a40/pluggy-1.3.0.tar.gz" 572 | } 573 | ], 574 | "project_name": "pluggy", 575 | "requires_dists": [ 576 | "pre-commit; extra == \"dev\"", 577 | "pytest-benchmark; extra == \"testing\"", 578 | "pytest; extra == \"testing\"", 579 | "tox; extra == \"dev\"" 580 | ], 581 | "requires_python": ">=3.8", 582 | "version": "1.3.0" 583 | }, 584 | { 585 | "artifacts": [ 586 | { 587 | "algorithm": "sha256", 588 | "hash": "f6f8dc65625dadaad0c8545319c2e2f0424fede988368893ca3844261342c11a", 589 | "url": "https://files.pythonhosted.org/packages/fa/8a/e9c6b48b8f4651df1b1a9d46fe94a74ed99881141b4660aa855a798c7c53/protobuf-4.24.3-py3-none-any.whl" 590 | }, 591 | { 592 | "algorithm": "sha256", 593 | "hash": "12e9ad2ec079b833176d2921be2cb24281fa591f0b119b208b788adc48c2561d", 594 | "url": "https://files.pythonhosted.org/packages/79/30/98dc7297ce8c3f0182800d2879703f0196e76d6a28e53ecaafc3901f8118/protobuf-4.24.3.tar.gz" 595 | }, 596 | { 597 | "algorithm": "sha256", 598 | "hash": "ba53c2f04798a326774f0e53b9c759eaef4f6a568ea7072ec6629851c8435959", 599 | "url": "https://files.pythonhosted.org/packages/90/76/4303d1f01e799ed0243a55ec81fb16a731faed0d7c95eaba809f4c7f408d/protobuf-4.24.3-cp37-abi3-manylinux2014_aarch64.whl" 600 | }, 601 | { 602 | "algorithm": "sha256", 603 | "hash": "f6ccbcf027761a2978c1406070c3788f6de4a4b2cc20800cc03d52df716ad675", 604 | "url": "https://files.pythonhosted.org/packages/bb/c3/6a06208ecf0934ecaf509b51c52a6cf688586f54ae81ac65c56124571494/protobuf-4.24.3-cp37-abi3-manylinux2014_x86_64.whl" 605 | }, 606 | { 607 | "algorithm": "sha256", 608 | "hash": "6e514e8af0045be2b56e56ae1bb14f43ce7ffa0f68b1c793670ccbe2c4fc7d2b", 609 | "url": "https://files.pythonhosted.org/packages/fe/f3/957db80e5b9f7fd7df97e5554fdc57919dfad24e89291223fd04a0e3c84f/protobuf-4.24.3-cp37-abi3-macosx_10_9_universal2.whl" 610 | } 611 | ], 612 | "project_name": "protobuf", 613 | "requires_dists": [], 614 | "requires_python": ">=3.7", 615 | "version": "4.24.3" 616 | }, 617 | { 618 | "artifacts": [ 619 | { 620 | "algorithm": "sha256", 621 | "hash": "1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002", 622 | "url": "https://files.pythonhosted.org/packages/df/d0/e192c4275aecabf74faa1aacd75ef700091913236ec78b1a98f62a2412ee/pytest-7.4.2-py3-none-any.whl" 623 | }, 624 | { 625 | "algorithm": "sha256", 626 | "hash": "a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069", 627 | "url": "https://files.pythonhosted.org/packages/e5/d0/18209bb95db8ee693a9a04fe056ab0663c6d6b1baf67dd50819dd9cd4bd7/pytest-7.4.2.tar.gz" 628 | } 629 | ], 630 | "project_name": "pytest", 631 | "requires_dists": [ 632 | "argcomplete; extra == \"testing\"", 633 | "attrs>=19.2.0; extra == \"testing\"", 634 | "colorama; sys_platform == \"win32\"", 635 | "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", 636 | "hypothesis>=3.56; extra == \"testing\"", 637 | "importlib-metadata>=0.12; python_version < \"3.8\"", 638 | "iniconfig", 639 | "mock; extra == \"testing\"", 640 | "nose; extra == \"testing\"", 641 | "packaging", 642 | "pluggy<2.0,>=0.12", 643 | "pygments>=2.7.2; extra == \"testing\"", 644 | "requests; extra == \"testing\"", 645 | "setuptools; extra == \"testing\"", 646 | "tomli>=1.0.0; python_version < \"3.11\"", 647 | "xmlschema; extra == \"testing\"" 648 | ], 649 | "requires_python": ">=3.7", 650 | "version": "7.4.2" 651 | }, 652 | { 653 | "artifacts": [ 654 | { 655 | "algorithm": "sha256", 656 | "hash": "c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e", 657 | "url": "https://files.pythonhosted.org/packages/4b/21/b65ecd6686da400e2f6e3c49c2a428325abd979c9670cd97e1671f53296e/pytest_django-4.5.2-py3-none-any.whl" 658 | }, 659 | { 660 | "algorithm": "sha256", 661 | "hash": "d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2", 662 | "url": "https://files.pythonhosted.org/packages/9b/42/6d6563165b82289d4a30ea477f85c04386303e51cf4e4e4651d4f9910830/pytest-django-4.5.2.tar.gz" 663 | } 664 | ], 665 | "project_name": "pytest-django", 666 | "requires_dists": [ 667 | "Django; extra == \"testing\"", 668 | "django-configurations>=2.0; extra == \"testing\"", 669 | "pytest>=5.4.0", 670 | "sphinx-rtd-theme; extra == \"docs\"", 671 | "sphinx; extra == \"docs\"" 672 | ], 673 | "requires_python": ">=3.5", 674 | "version": "4.5.2" 675 | }, 676 | { 677 | "artifacts": [ 678 | { 679 | "algorithm": "sha256", 680 | "hash": "ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7", 681 | "url": "https://files.pythonhosted.org/packages/32/4d/aaf7eff5deb402fd9a24a1449a8119f00d74ae9c2efa79f8ef9994261fc2/pytz-2023.3.post1-py2.py3-none-any.whl" 682 | }, 683 | { 684 | "algorithm": "sha256", 685 | "hash": "7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", 686 | "url": "https://files.pythonhosted.org/packages/69/4f/7bf883f12ad496ecc9514cd9e267b29a68b3e9629661a2bbc24f80eff168/pytz-2023.3.post1.tar.gz" 687 | } 688 | ], 689 | "project_name": "pytz", 690 | "requires_dists": [], 691 | "requires_python": null, 692 | "version": "2023.3.post1" 693 | }, 694 | { 695 | "artifacts": [ 696 | { 697 | "algorithm": "sha256", 698 | "hash": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", 699 | "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl" 700 | }, 701 | { 702 | "algorithm": "sha256", 703 | "hash": "942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", 704 | "url": "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz" 705 | } 706 | ], 707 | "project_name": "requests", 708 | "requires_dists": [ 709 | "PySocks!=1.5.7,>=1.5.6; extra == \"socks\"", 710 | "certifi>=2017.4.17", 711 | "chardet<6,>=3.0.2; extra == \"use_chardet_on_py3\"", 712 | "charset-normalizer<4,>=2", 713 | "idna<4,>=2.5", 714 | "urllib3<3,>=1.21.1" 715 | ], 716 | "requires_python": ">=3.7", 717 | "version": "2.31.0" 718 | }, 719 | { 720 | "artifacts": [ 721 | { 722 | "algorithm": "sha256", 723 | "hash": "b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a", 724 | "url": "https://files.pythonhosted.org/packages/bb/26/7945080113158354380a12ce26873dd6c1ebd88d47f5bc24e2c5bb38c16a/setuptools-68.2.2-py3-none-any.whl" 725 | }, 726 | { 727 | "algorithm": "sha256", 728 | "hash": "4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87", 729 | "url": "https://files.pythonhosted.org/packages/ef/cc/93f7213b2ab5ed383f98ce8020e632ef256b406b8569606c3f160ed8e1c9/setuptools-68.2.2.tar.gz" 730 | } 731 | ], 732 | "project_name": "setuptools", 733 | "requires_dists": [ 734 | "build[virtualenv]; extra == \"testing\"", 735 | "build[virtualenv]>=1.0.3; extra == \"testing-integration\"", 736 | "filelock>=3.4.0; extra == \"testing\"", 737 | "filelock>=3.4.0; extra == \"testing-integration\"", 738 | "flake8-2020; extra == \"testing\"", 739 | "furo; extra == \"docs\"", 740 | "ini2toml[lite]>=0.9; extra == \"testing\"", 741 | "jaraco.develop>=7.21; (python_version >= \"3.9\" and sys_platform != \"cygwin\") and extra == \"testing\"", 742 | "jaraco.envs>=2.2; extra == \"testing\"", 743 | "jaraco.envs>=2.2; extra == \"testing-integration\"", 744 | "jaraco.packaging>=9.3; extra == \"docs\"", 745 | "jaraco.path>=3.2.0; extra == \"testing\"", 746 | "jaraco.path>=3.2.0; extra == \"testing-integration\"", 747 | "jaraco.tidelift>=1.4; extra == \"docs\"", 748 | "packaging>=23.1; extra == \"testing-integration\"", 749 | "pip>=19.1; extra == \"testing\"", 750 | "pygments-github-lexers==0.0.5; extra == \"docs\"", 751 | "pytest-black>=0.3.7; platform_python_implementation != \"PyPy\" and extra == \"testing\"", 752 | "pytest-checkdocs>=2.4; extra == \"testing\"", 753 | "pytest-cov; platform_python_implementation != \"PyPy\" and extra == \"testing\"", 754 | "pytest-enabler; extra == \"testing-integration\"", 755 | "pytest-enabler>=2.2; extra == \"testing\"", 756 | "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", 757 | "pytest-perf; sys_platform != \"cygwin\" and extra == \"testing\"", 758 | "pytest-ruff; sys_platform != \"cygwin\" and extra == \"testing\"", 759 | "pytest-timeout; extra == \"testing\"", 760 | "pytest-xdist; extra == \"testing\"", 761 | "pytest-xdist; extra == \"testing-integration\"", 762 | "pytest; extra == \"testing-integration\"", 763 | "pytest>=6; extra == \"testing\"", 764 | "rst.linker>=1.9; extra == \"docs\"", 765 | "sphinx-favicon; extra == \"docs\"", 766 | "sphinx-hoverxref<2; extra == \"docs\"", 767 | "sphinx-inline-tabs; extra == \"docs\"", 768 | "sphinx-lint; extra == \"docs\"", 769 | "sphinx-notfound-page<2,>=1; extra == \"docs\"", 770 | "sphinx-reredirects; extra == \"docs\"", 771 | "sphinx>=3.5; extra == \"docs\"", 772 | "sphinxcontrib-towncrier; extra == \"docs\"", 773 | "tomli-w>=1.0.0; extra == \"testing\"", 774 | "tomli; extra == \"testing-integration\"", 775 | "virtualenv>=13.0.0; extra == \"testing\"", 776 | "virtualenv>=13.0.0; extra == \"testing-integration\"", 777 | "wheel; extra == \"testing\"", 778 | "wheel; extra == \"testing-integration\"" 779 | ], 780 | "requires_python": ">=3.8", 781 | "version": "68.2.2" 782 | }, 783 | { 784 | "artifacts": [ 785 | { 786 | "algorithm": "sha256", 787 | "hash": "5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", 788 | "url": "https://files.pythonhosted.org/packages/98/5a/66d7c9305baa9f11857f247d4ba761402cea75db6058ff850ed7128957b7/sqlparse-0.4.4-py3-none-any.whl" 789 | }, 790 | { 791 | "algorithm": "sha256", 792 | "hash": "d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c", 793 | "url": "https://files.pythonhosted.org/packages/65/16/10f170ec641ed852611b6c9441b23d10b5702ab5288371feab3d36de2574/sqlparse-0.4.4.tar.gz" 794 | } 795 | ], 796 | "project_name": "sqlparse", 797 | "requires_dists": [ 798 | "build; extra == \"dev\"", 799 | "flake8; extra == \"dev\"", 800 | "pytest-cov; extra == \"test\"", 801 | "pytest; extra == \"test\"", 802 | "sphinx; extra == \"doc\"" 803 | ], 804 | "requires_python": ">=3.5", 805 | "version": "0.4.4" 806 | }, 807 | { 808 | "artifacts": [ 809 | { 810 | "algorithm": "sha256", 811 | "hash": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 812 | "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl" 813 | }, 814 | { 815 | "algorithm": "sha256", 816 | "hash": "de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", 817 | "url": "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz" 818 | } 819 | ], 820 | "project_name": "tomli", 821 | "requires_dists": [], 822 | "requires_python": ">=3.7", 823 | "version": "2.0.1" 824 | }, 825 | { 826 | "artifacts": [ 827 | { 828 | "algorithm": "sha256", 829 | "hash": "cebfb004989d9a2ab0d24c0c5805783c7f4e07243ea4ed2a8f1809d072bf712b", 830 | "url": "https://files.pythonhosted.org/packages/e1/54/df0d3e62636f291d716caa44c5c9590af8e6f225c59d7874051da6428c06/translate-3.6.1-py2.py3-none-any.whl" 831 | }, 832 | { 833 | "algorithm": "sha256", 834 | "hash": "7e70ffa46f193cc744be7c88b8e1323f10f6b2bb90d24bb5d29fdf1e56618783", 835 | "url": "https://files.pythonhosted.org/packages/d1/9d/692066b9e26176a93ce627bf467bca48f198eb0e036337a5180d566c9561/translate-3.6.1.tar.gz" 836 | } 837 | ], 838 | "project_name": "translate", 839 | "requires_dists": [ 840 | "click", 841 | "libretranslatepy==2.1.1", 842 | "lxml", 843 | "requests" 844 | ], 845 | "requires_python": null, 846 | "version": "3.6.1" 847 | }, 848 | { 849 | "artifacts": [ 850 | { 851 | "algorithm": "sha256", 852 | "hash": "1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf", 853 | "url": "https://files.pythonhosted.org/packages/79/4f/58c28f50233612d9890cc2b068aee977ef3f7d6434ef4debf98162e34152/types_pytz-2023.3.1.1-py3-none-any.whl" 854 | }, 855 | { 856 | "algorithm": "sha256", 857 | "hash": "cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a", 858 | "url": "https://files.pythonhosted.org/packages/a7/33/6f8feb17652548d68f0bab14c7f810419ffc43bd30792d54a89289ac2b6f/types-pytz-2023.3.1.1.tar.gz" 859 | } 860 | ], 861 | "project_name": "types-pytz", 862 | "requires_dists": [], 863 | "requires_python": null, 864 | "version": "2023.3.1.1" 865 | }, 866 | { 867 | "artifacts": [ 868 | { 869 | "algorithm": "sha256", 870 | "hash": "c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24", 871 | "url": "https://files.pythonhosted.org/packages/9d/df/aabb870a04254ceb8a406b0a4222c1b14f7fdf3d2d7633ba49364aca27f3/types_PyYAML-6.0.12.12-py3-none-any.whl" 872 | }, 873 | { 874 | "algorithm": "sha256", 875 | "hash": "334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062", 876 | "url": "https://files.pythonhosted.org/packages/af/48/b3bbe63a129a80911b60f57929c5b243af909bc1c9590917434bca61a4a3/types-PyYAML-6.0.12.12.tar.gz" 877 | } 878 | ], 879 | "project_name": "types-pyyaml", 880 | "requires_dists": [], 881 | "requires_python": null, 882 | "version": "6.0.12.12" 883 | }, 884 | { 885 | "artifacts": [ 886 | { 887 | "algorithm": "sha256", 888 | "hash": "c7a9d6b62776f21b169a94a0e9d2dfcae62fa9149f53594ff791c3ae67325490", 889 | "url": "https://files.pythonhosted.org/packages/ce/7f/0e94566840d36a44d17921d47ff8536e0c1f129113e40cb9718564213672/types_requests-2.31.0.4-py3-none-any.whl" 890 | }, 891 | { 892 | "algorithm": "sha256", 893 | "hash": "a111041148d7e04bf100c476bc4db3ee6b0a1cd0b4018777f6a660b1c4f1318d", 894 | "url": "https://files.pythonhosted.org/packages/df/6f/67b641d3fb005fba2306056057b49cc351bf41b11277e1ef69ce0d7798e2/types-requests-2.31.0.4.tar.gz" 895 | } 896 | ], 897 | "project_name": "types-requests", 898 | "requires_dists": [ 899 | "types-urllib3" 900 | ], 901 | "requires_python": null, 902 | "version": "2.31.0.4" 903 | }, 904 | { 905 | "artifacts": [ 906 | { 907 | "algorithm": "sha256", 908 | "hash": "9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e", 909 | "url": "https://files.pythonhosted.org/packages/11/7b/3fc711b2efea5e85a7a0bbfe269ea944aa767bbba5ec52f9ee45d362ccf3/types_urllib3-1.26.25.14-py3-none-any.whl" 910 | }, 911 | { 912 | "algorithm": "sha256", 913 | "hash": "229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", 914 | "url": "https://files.pythonhosted.org/packages/73/de/b9d7a68ad39092368fb21dd6194b362b98a1daeea5dcfef5e1adb5031c7e/types-urllib3-1.26.25.14.tar.gz" 915 | } 916 | ], 917 | "project_name": "types-urllib3", 918 | "requires_dists": [], 919 | "requires_python": null, 920 | "version": "1.26.25.14" 921 | }, 922 | { 923 | "artifacts": [ 924 | { 925 | "algorithm": "sha256", 926 | "hash": "8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", 927 | "url": "https://files.pythonhosted.org/packages/24/21/7d397a4b7934ff4028987914ac1044d3b7d52712f30e2ac7a2ae5bc86dd0/typing_extensions-4.8.0-py3-none-any.whl" 928 | }, 929 | { 930 | "algorithm": "sha256", 931 | "hash": "df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef", 932 | "url": "https://files.pythonhosted.org/packages/1f/7a/8b94bb016069caa12fc9f587b28080ac33b4fbb8ca369b98bc0a4828543e/typing_extensions-4.8.0.tar.gz" 933 | } 934 | ], 935 | "project_name": "typing-extensions", 936 | "requires_dists": [], 937 | "requires_python": ">=3.8", 938 | "version": "4.8.0" 939 | }, 940 | { 941 | "artifacts": [ 942 | { 943 | "algorithm": "sha256", 944 | "hash": "ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e", 945 | "url": "https://files.pythonhosted.org/packages/37/dc/399e63f5d1d96bb643404ee830657f4dfcf8503f5ba8fa3c6d465d0c57fe/urllib3-2.0.5-py3-none-any.whl" 946 | }, 947 | { 948 | "algorithm": "sha256", 949 | "hash": "13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594", 950 | "url": "https://files.pythonhosted.org/packages/51/13/62cb4a0af89fdf72db4a0ead8026e724c7f3cbf69706d84a4eff439be853/urllib3-2.0.5.tar.gz" 951 | } 952 | ], 953 | "project_name": "urllib3", 954 | "requires_dists": [ 955 | "brotli>=1.0.9; platform_python_implementation == \"CPython\" and extra == \"brotli\"", 956 | "brotlicffi>=0.8.0; platform_python_implementation != \"CPython\" and extra == \"brotli\"", 957 | "certifi; extra == \"secure\"", 958 | "cryptography>=1.9; extra == \"secure\"", 959 | "idna>=2.0.0; extra == \"secure\"", 960 | "pyopenssl>=17.1.0; extra == \"secure\"", 961 | "pysocks!=1.5.7,<2.0,>=1.5.6; extra == \"socks\"", 962 | "urllib3-secure-extra; extra == \"secure\"", 963 | "zstandard>=0.18.0; extra == \"zstd\"" 964 | ], 965 | "requires_python": ">=3.7", 966 | "version": "2.0.5" 967 | } 968 | ], 969 | "platform_tag": null 970 | } 971 | ], 972 | "path_mappings": {}, 973 | "pex_version": "2.1.134", 974 | "pip_version": "20.3.4-patched", 975 | "prefer_older_binary": false, 976 | "requirements": [ 977 | "ansicolors>=1.0.2", 978 | "django-debug-toolbar<4,>=3.8.0", 979 | "django-stubs==1.10.1", 980 | "django<4,>=3.2.13", 981 | "gunicorn>=20.1.0", 982 | "mypy==0.942", 983 | "protobuf>=3.11.3", 984 | "pytest-django<5,>=4", 985 | "pytest>=6.0.1", 986 | "requests>=2.25.1", 987 | "setuptools>=42.0.0", 988 | "translate>=3.2.1", 989 | "types-requests>=2.25.1" 990 | ], 991 | "requires_python": [ 992 | "<3.10,>=3.9" 993 | ], 994 | "resolver_version": "pip-2020-resolver", 995 | "style": "universal", 996 | "target_systems": [ 997 | "linux", 998 | "mac" 999 | ], 1000 | "transitive": true, 1001 | "use_pep517": null 1002 | } 1003 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.26.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.9,<3.10"] 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 = ["django-stubs"] 57 | interpreter_constraints = [">=3.9,<3.10"] 58 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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==1.10.1 8 | gunicorn>=20.1.0 9 | mypy==0.942 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 | --------------------------------------------------------------------------------