Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit bd4446d

Browse files
refactor(cli): remove pkg in favour of automatically downloading Node (#454)
## Change Summary This PR completely refactors how the Prisma CLI is downloaded / installed / called. We now download Node itself at runtime and use that to install the Prisma CLI and then run it directly with Node as well. This has some significant advantages: - We are now no longer tied to releases of the packaged CLI - These were being released by Luca, who created the Go Client and is no longer at Prisma, on his own free time. - Only major versions were released which means the CLI couldn't be easily tested with the latest changes on the https://github.com/prisma/prisma repository - Prisma Studio can now be launched from the CLI - The TypeScript Client can now be generated from our CLI wrapper - We now longer have to manually download the engine binaries ourselves, that's handled transparently for us - A packaged version of Node no longer has to be installed for each new Prisma version - We now have support for ARM However, this does not come without some concerns: - Size increase - We use https://github.com/ekalinin/nodeenv to download Node at runtime if it isn't already installed. This downloads and creates extra files that are not strictly necessary for our use case. This results in an increase from ~140MB -> ~300MB. - - However this size increase can be reduced by installing [nodejs-bin](https://pypi.org/project/nodejs-bin/) which you can do by providing the `node` extra, e.g. `pip install prisma[node]`. This brings the total download size to be very similar to the packaged CLI. - This concern also doesn't apply in cases where Node is already present. It actually will greatly improve the experience in this case. ## How does it work? We now resolve a Node binary using this flow: - Check if [nodejs-bin](https://pypi.org/project/nodejs-bin/) is installed - Check if Node is installed globally - Downloads Node using https://github.com/ekalinin/nodeenv to a configurable location which defaults to `~/.cache/prisma-nodeenv` The first two steps in this flow can be skipped if you so desire through your `pyproject.toml` file or using environment variables. For example: ```toml [tool.prisma] # skip global node check use_global_node = false # skip nodejs-bin check use_nodejs_bin = false # change nodeenv installation directory nodeenv_cache_dir = '~/.foo/nodeenv' ``` Or using the environment variables, `PRISMA_USE_GLOBAL_NODE`, `PRISMA_USE_NODEJS_BIN` and `PRISMA_NODEENV_CACHE_DIR` respectively. The Prisma CLI is then installed directly from [npm](https://www.npmjs.com/package/prisma) and ran directly using the resolved Node binary.
1 parent 87d5e37 commit bd4446d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1227
-517
lines changed

.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@
44
!src/
55
!tests
66
!requirements/
7+
!databases/
8+
!lib/
9+
!noxfile.py
710
!pipelines/
11+
!pytest.ini
12+
!MANIFEST.in

.github/workflows/test.yml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
name: test
2222
runs-on: ${{ matrix.os }}
2323
strategy:
24+
fail-fast: false
2425
matrix:
2526
os: [ubuntu-latest, windows-latest]
2627
python-version: [3.7, 3.8, 3.9, "3.10", "3.11.0-rc.1"]
@@ -303,21 +304,23 @@ jobs:
303304
strategy:
304305
fail-fast: false
305306
matrix:
306-
docker-platform: [linux/amd64]
307-
# TODO: Uncomment this to add testing support for arm64 and delete
308-
# the above
309-
# docker-platform: ["linux/amd64", "linux/arm64"]
310-
# TODO: Uncomment this later, Go-based CLI does not run on Alpine
311-
# https://github.com/prisma/prisma-client-go/issues/357
312-
# python-os-distro: [slim-bullseye, alpine]
313-
python-os-distro: [slim-bullseye]
307+
docker-platform: [linux/amd64, linux/arm64]
308+
python-os-distro: [slim-bullseye, alpine]
309+
exclude:
310+
# Dockerfile is currently broken for alpine / arm64
311+
# https://github.com/RobertCraigie/prisma-client-py/issues/581
312+
- docker-platform: linux/arm64
313+
python-os-distro: alpine
314314
steps:
315315
- uses: actions/checkout@v3
316+
316317
# https://github.com/docker/build-push-action/
317318
- name: Set up QEMU
318319
uses: docker/setup-qemu-action@v2
320+
319321
- name: Set up Docker Buildx
320322
uses: docker/setup-buildx-action@v2
323+
321324
- name: Docker Build
322325
uses: docker/build-push-action@v3
323326
# https://github.com/docker/build-push-action/#inputs

databases/main.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
from jinja2 import Environment, FileSystemLoader, StrictUndefined
2222

2323
from lib.utils import flatten, escape_path
24-
from pipelines.utils import setup_coverage, get_pkg_location
24+
from pipelines.utils import (
25+
setup_coverage,
26+
get_pkg_location,
27+
maybe_install_nodejs_bin,
28+
)
2529
from prisma._compat import cached_property
2630

2731
from .utils import DatabaseConfig
@@ -68,6 +72,8 @@ def test(
6872
with session.chdir(DATABASES_DIR):
6973
# setup env
7074
session.install('-r', 'requirements.txt')
75+
maybe_install_nodejs_bin(session)
76+
7177
if inplace: # pragma: no cover
7278
# useful for updating the generated code so that Pylance picks it up
7379
session.install('-U', '-e', '..')

databases/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ click==8.1.3
33
coverage==6.5.0
44
syrupy==3.0.5
55
dirty-equals==0.5.0
6+
distro
67

78
-r ../pipelines/requirements/deps/pyright.txt
89
-r ../pipelines/requirements/deps/pytest.txt

docs/reference/config.md

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -199,54 +199,87 @@ Or through environment variables, e.g. `PRISMA_BINARY_CACHE_DIR`. In the case th
199199

200200
### Binary Cache Directory
201201

202-
This option controls where the Prisma Engine and Prisma CLI binaries should be downloaded to. This defaults to a temporary directory that includes the current Prisma Engine version.
202+
This option controls where the Prisma Engine and Prisma CLI binaries should be downloaded to. This defaults to a cache directory that includes the current Prisma Engine version.
203+
204+
| Option | Environment Variable | Default |
205+
| ------------------ | -------------------------- | ------------------------------------------------------------------------- |
206+
| `binary_cache_dir` | `PRISMA_BINARY_CACHE_DIR` | `/{home}/.cache/prisma-python/binaries/{prisma_version}/{engine_version}` |
207+
208+
### Home Directory
209+
210+
This option can be used to change the base directory of the `binary_cache_dir` option without having to worry about versioning the Prisma binaries. This is useful if you need to download the binaries to a local directory.
211+
212+
| Option | Environment Variable | Default |
213+
| -------- | --------------------- | ------- |
214+
| `home_dir` | `PRISMA_HOME_DIR` | `~` |
203215

204-
| Option | Environment Variable | Default |
205-
| ------------------ | -------------------------- | ----------------------------------------------------- |
206-
| `binary_cache_dir` | `PRISMA_BINARY_CACHE_DIR` | `/{tmp}/prisma/binaries/engines/{engine_version}` |
207216

208217
### Prisma Version
209218

210-
This option controls the version of the Prisma CLI to use. It should be noted that this is intended to be internal and only the pinned prisma version is guaranteed to be supported.
219+
This option controls the version of Prisma to use. It should be noted that this is intended to be internal and only the pinned Prisma version is guaranteed to be supported.
211220

212221
| Option | Environment Variable | Default |
213222
| ---------------- | --------------------- | -------- |
214223
| `prisma_version` | `PRISMA_VERSION` | `3.13.0` |
215224

216-
### Engine Version
225+
### Expected Engine Version
217226

218-
This option controls the version of the [Prisma Engines](https://github.com/prisma/prisma-engines) to use, like `prisma_version` this is intended to be internal and only the pinned engine version is guaranteed to be supported.
227+
This is an internal option that is here as a safeguard for the `prisma_version` option. If you modify the `prisma_version` option then you must also update this option to use the corresponding engine version. You can find a list of engine versions [here](https://github.com/prisma/prisma-engines).
219228

220-
| Option | Environment Variable | Default |
221-
| ---------------- | ----------------------- | ------------------------------------------ |
222-
| `engine_version` | `PRISMA_ENGINE_VERSION` | `efdf9b1183dddfd4258cd181a72125755215ab7b` |
229+
| Option | Environment Variable | Default |
230+
| ------------------------- | -------------------------------- | ------------------------------------------ |
231+
| `expected_engine_version` | `PRISMA_EXPECTED_ENGINE_VERSION` | `efdf9b1183dddfd4258cd181a72125755215ab7b` |
223232

224-
### Prisma URL
225233

226-
This option controls where the Prisma CLI binaries should be downloaded from. If set, this must be a string that takes two format arguments, `version` and `platform`, for example:
234+
### Binary Platform
235+
236+
This option is useful if you need to make use of the [binaryTargets](https://www.prisma.io/docs/concepts/components/prisma-schema/generators#binary-targets) schema option to build your application on one platform and deploy it on another.
237+
238+
This allows you to set the current platform dynamically as Prisma Client Python does not have official support for `binaryTargets` and although we do have some safe guards in place to attempt to use the correct binary, it has not been thoroughly tested.
239+
240+
A list of valid options can be found [here](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#binarytargets-options).
227241

228-
```
229-
https://example.com/prisma-cli-{version}-{platform}.gz
230-
```
231242

232-
| Option | Environment Variable | Default |
233-
| -------------| -------------------- | --------------------------------------------------------------------------------------- |
234-
| `prisma_url` | `PRISMA_CLI_URL` | `https://prisma-photongo.s3-eu-west-1.amazonaws.com/prisma-cli-{version}-{platform}.gz` |
243+
| Option | Environment Variable |
244+
| ----------------- | ------------------------ |
245+
| `binary_platform` | `PRISMA_BINARY_PLATFORM` |
235246

236-
### Engine URL
247+
### Use Global Node
237248

238-
This option controls where the [Prisma Engine](https://github.com/prisma/prisma-engines) binaries should be downloaded from. If set, this must be a string that takes three positional format arguments, for example:
249+
This option configures whether or not Prisma Client Python will attempt to use the globally installed version of [Node](https://nodejs.org/en/), if it is available, to run the Prisma CLI.
250+
251+
| Option | Environment Variable | Default |
252+
| ----------------- | ------------------------ | ------- |
253+
| `use_global_node` | `PRISMA_USE_GLOBAL_NODE` | `True` |
254+
255+
### Use nodejs-bin
256+
257+
This option configures whether or not Prisma Client Python will attempt to use the installed version of the [nodejs-bin](https://pypi.org/project/nodejs-bin/) package, if it is available, to run the Prisma CLI.
258+
259+
| Option | Environment Variable | Default |
260+
| ---------------- | ----------------------- | ------- |
261+
| `use_nodejs_bin` | `PRISMA_USE_NODEJS_BIN` | `True` |
262+
263+
### Extra Nodeenv Arguments
264+
265+
This option allows you to pass additional arguments to [nodeenv](https://github.com/ekalinin/nodeenv) which is the package we use to automatically download a Node binary to run the CLI with.
266+
267+
Arguments are passed after the path, for example:
239268

240269
```
241-
https://example.com/prisma-binaries-mirror/{0}/{1}/{2}.gz
270+
python -m nodeenv <path> <extra args>
242271
```
243272

244-
Where:
273+
| Option | Environment Variable |
274+
| -------------------- | --------------------------- |
275+
| `nodeenv_extra_args` | `PRISMA_NODEENV_EXTRA_ARGS` |
276+
277+
### Nodeenv Cache Directory
278+
279+
This option configures where Prisma Client Python will store the Node binary that is installed by [nodeenv](https://github.com/ekalinin/nodeenv).
245280

246-
- `0` corresponds to the [engine version](#engine-version)
247-
- `1` corresponds to the current binary platform
248-
- `2` corresponds to the name of the engine being downloaded.
281+
Note that this does not make use of the [Home Directory](#home-directory) option and instead uses the actual user home directory.
249282

250-
| Option | Environment Variable | Default |
251-
| -------------| -------------------- | ------------------------------------------------------- |
252-
| `engine_url` | `PRISMA_ENGINE_URL` | `https://binaries.prisma.sh/all_commits/{0}/{1}/{2}.gz` |
283+
| Option | Environment Variable | Default |
284+
| ------------------- | -------------------------- | --------------------------------- |
285+
| `nodeenv_cache_dir` | `PRISMA_NODEENV_CACHE_DIR` | `~/.cache/prisma-python/nodeenv/` |

lib/testing/shared_conftest/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# NOTE: a lot of these are not intended to be imported and referenced directly
22
# and are instead intended to be `*` imported in a `conftest.py` file
33
from ._shared_conftest import (
4+
setup_env as setup_env,
45
event_loop as event_loop,
56
client_fixture as client_fixture,
67
patch_prisma_fixture as patch_prisma_fixture,

lib/testing/shared_conftest/_shared_conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import inspect
33
from typing import TYPE_CHECKING, Iterator
4+
from pathlib import Path
45

56
import pytest
67

@@ -14,6 +15,10 @@
1415

1516
if TYPE_CHECKING:
1617
from _pytest.fixtures import FixtureRequest
18+
from _pytest.monkeypatch import MonkeyPatch
19+
20+
21+
HOME_DIR = Path.home()
1722

1823

1924
@async_fixture(name='client', scope='session')
@@ -31,6 +36,14 @@ def event_loop() -> asyncio.AbstractEventLoop:
3136
return get_or_create_event_loop()
3237

3338

39+
@pytest.fixture(autouse=True)
40+
def setup_env(monkeypatch: 'MonkeyPatch') -> None:
41+
# Set a custom home directory to use for caching binaries so that
42+
# when we make use of pytest's temporary directory functionality the binaries
43+
# don't have to be downloaded again.
44+
monkeypatch.setenv('PRISMA_HOME_DIR', str(HOME_DIR))
45+
46+
3447
@pytest.fixture(name='patch_prisma', autouse=True)
3548
def patch_prisma_fixture(request: 'FixtureRequest') -> Iterator[None]:
3649
if request_has_client(request):

pipelines/coverage.nox.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from pathlib import Path
55

66
import nox
7-
from git.repo import Repo
87

98
from lib.utils import maybe_decode
109
from pipelines.utils import setup_env, CACHE_DIR, TMP_DIR
@@ -18,6 +17,10 @@
1817

1918
@nox.session(name='push-coverage')
2019
def push_coverage(session: nox.Session) -> None:
20+
# We have to import `git` here as it will cause an error on machines that don't have
21+
# git installed. This happens in our docker tests.
22+
from git.repo import Repo
23+
2124
session.env['COVERAGE_FILE'] = str(CACHE_DIR / '.coverage')
2225
session.install(
2326
'-r',

pipelines/requirements/dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ twine==4.0.1
66
typer==0.7.0
77
rtoml==0.9.0
88
GitPython
9+
distro

pipelines/requirements/lint.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
-r test.txt
2+
-r node.txt
23
-r deps/pyright.txt
34
interrogate==1.5.0
45
blue==0.9.1

pipelines/requirements/mypy.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
-r test.txt
2+
-r node.txt
23
mypy==0.950
34
types-mock

pipelines/requirements/node.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs-bin==16.15.1a4

pipelines/test.nox.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import nox
22

3-
from pipelines.utils import setup_env
3+
from pipelines.utils import setup_env, maybe_install_nodejs_bin
44
from pipelines.utils.prisma import generate
55

66

@@ -9,6 +9,7 @@ def test(session: nox.Session) -> None:
99
setup_env(session)
1010
session.install('-r', 'pipelines/requirements/test.txt')
1111
session.install('.')
12+
maybe_install_nodejs_bin(session)
1213

1314
generate(session)
1415

pipelines/utils/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
from pathlib import Path
33

44
import nox
5+
import distro
56

67

78
CACHE_DIR = Path.cwd() / '.cache'
89
TMP_DIR = Path(tempfile.gettempdir())
10+
PIPELINES_DIR = Path(__file__).parent.parent
911

1012

1113
def get_pkg_location(session: nox.Session, pkg: str) -> str:
@@ -44,3 +46,13 @@ def setup_env(session: nox.Session) -> None:
4446
]
4547
]
4648
)
49+
50+
51+
def maybe_install_nodejs_bin(session: nox.Session) -> bool:
52+
# nodejs-bin is not available on alpine yet, we need to wait until this fix is released:
53+
# https://github.com/samwillis/nodejs-pypi/issues/11
54+
if distro.id() == 'alpine':
55+
return False
56+
57+
session.install('-r', str(PIPELINES_DIR / 'requirements/node.txt'))
58+
return True

requirements/base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ click>=7.1.2
55
python-dotenv>=0.12.0
66
typing-extensions>=3.7
77
tomlkit
8+
nodeenv
89
cached-property; python_version < '3.8'

requirements/node.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs-bin

scripts/docs.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,13 @@ def main() -> None:
4646
r'\1' + '`' + config.prisma_version + '`',
4747
content,
4848
)
49+
4950
content = re.sub(
50-
r'(`PRISMA_ENGINE_VERSION` \| )`(.*)`',
51-
r'\1' + '`' + config.engine_version + '`',
52-
content,
53-
)
54-
content = re.sub(
55-
r'(`PRISMA_CLI_URL` \| )`(.*)`',
56-
r'\1' + '`' + config.prisma_url + '`',
57-
content,
58-
)
59-
content = re.sub(
60-
r'(`PRISMA_ENGINE_URL` \| )`(.*)`',
61-
r'\1' + '`' + config.engine_url + '`',
51+
r'(`PRISMA_EXPECTED_ENGINE_VERSION` \| )`(.*)`',
52+
r'\1' + '`' + config.expected_engine_version + '`',
6253
content,
6354
)
55+
6456
config_doc.write_text(content)
6557

6658

setup.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ def requirements(name: str) -> List[str]:
2828
raise RuntimeError('version is not set')
2929

3030

31+
extras = {
32+
'node': requirements('node.txt'),
33+
}
34+
3135
setup(
3236
name='prisma',
3337
version=version,
@@ -50,8 +54,10 @@ def requirements(name: str) -> List[str]:
5054
include_package_data=True,
5155
zip_safe=False,
5256
extras_require={
53-
# we define `all` even though it's empty so that we can add to it in the future
54-
'all': [],
57+
**extras,
58+
'all': [
59+
req for requirements in extras.values() for req in requirements
60+
],
5561
},
5662
entry_points={
5763
'console_scripts': [

0 commit comments

Comments
 (0)