Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(binaries): improve platform and architecture support #233

Closed
wants to merge 64 commits into from
Closed
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
325091c
feat(fetch_binaries): add `distro` requirement
HKGx Jan 19, 2022
6d5f61e
feat(fetch_binaris): update `platform` to comply with original impl
HKGx Jan 19, 2022
44534d9
feat(fetch_binaries): forgot to import sys in platform.py
HKGx Jan 19, 2022
95bcd67
feat(fetch_binaries): missing `:`
HKGx Jan 19, 2022
d24d83b
feat(fetch_binaries): working prototype
HKGx Jan 20, 2022
66935b4
chore: some minor cleanup and try fix linux CLI downloading
RobertCraigie Jan 20, 2022
79fbf5c
chore: fix python3.7
RobertCraigie Jan 20, 2022
d9b5fa2
feat: change OsSettings from TypedDict to basic class
HKGx Jan 20, 2022
f510d98
chore: clean up tests/test_binaries.py from old mechanics
HKGx Jan 20, 2022
789b52d
chore: remove unnecessary Path call
HKGx Jan 20, 2022
ff838c0
chore: comment out legacy ensure() function
HKGx Jan 20, 2022
5dea8cf
chore: use ensure_cached() instead of ensure()
HKGx Jan 20, 2022
ced6e60
chore: comment out local path test
HKGx Jan 20, 2022
df6ef39
chore: import PRISMA_VERSION from prisma.binaries
HKGx Jan 20, 2022
f60e9b0
chore(pre-commit.ci): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 20, 2022
e160860
feat: add `is_windows` method on OsSettings
HKGx Jan 20, 2022
18f2a80
chore: use is_windows in query template
HKGx Jan 20, 2022
0ac3b3b
Merge remote-tracking branch 'HKGx/wip/fetch_binaries' into wip/fetch…
HKGx Jan 20, 2022
a30b98f
chore: linter use lazy string formatting for logging
HKGx Jan 21, 2022
98605a0
chore: disable tests that were using `utils.ensure()`
HKGx Jan 21, 2022
3485516
chore: add types to prisma/binaries/binaries.py
HKGx Jan 21, 2022
806a2f7
Merge branch 'main' into wip/fetch_binaries
RobertCraigie Jan 21, 2022
aa121ba
chore: change old usage of binary_platform to PLATFORM
HKGx Jan 21, 2022
ee1d2fd
chore: sort imports in prisma/binaries/
HKGx Jan 21, 2022
279bd6f
Merge remote-tracking branch 'HKGx/wip/fetch_binaries' into wip/fetch…
HKGx Jan 21, 2022
6fa32ff
feat: implement download method on `Binary`
HKGx Jan 21, 2022
c9e6ac3
feat: add `remove` method on Binary classs
HKGx Jan 24, 2022
2b4405b
chore: make download more streamlined
HKGx Jan 24, 2022
d88afb6
chore(binaries.py): forgot an import
HKGx Jan 24, 2022
c9422cc
chore(platform.py): add pyright directive to suppress stub errors
HKGx Jan 24, 2022
1069d1d
chore(test_fetch.py): skip fetch tests when on windows
HKGx Jan 24, 2022
3cfadd2
chore(pre-commit.ci): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 24, 2022
0158efb
chore(test_fetch.py): remove randomly placed letter
HKGx Jan 24, 2022
bc07433
Merge remote-tracking branch 'HKGx/wip/fetch_binaries' into wip/fetch…
HKGx Jan 24, 2022
d319391
chore(pre-commit.ci): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 24, 2022
c1c321a
feat(platform.py): download 1.1.x binaries when using openssl 3
HKGx Jan 25, 2022
c789342
chore: update snapshots
HKGx Jan 25, 2022
05217e2
chore: remove missing_ok from binary.remove
HKGx Jan 25, 2022
125e607
chore(binaries.py): add lazy logging format
HKGx Jan 25, 2022
4b4fc5f
chore(platform.py): fix linter errors by modularizing platform resolving
HKGx Jan 25, 2022
115a5dd
chore(binaries.py): fix linter errors by splitting up long string
HKGx Jan 25, 2022
63e8ec5
chore: remove empty test
HKGx Jan 25, 2022
d4d5df1
chore: remove unused imports
HKGx Jan 25, 2022
550d2ff
chore(platform.py): mypy ignore stubs from distro module
HKGx Jan 25, 2022
74c3381
chore: fix _compat typechecking
HKGx Jan 27, 2022
9bf32e1
chore: fix typo in "centos"
HKGx Jan 27, 2022
9d74f41
feat: major refactor of binaries.py
HKGx Jan 27, 2022
42aac70
chore: update snapshots
HKGx Jan 27, 2022
72a93e5
feat: add and rework important tests
HKGx Jan 27, 2022
61c2c12
chore: add pragmas to tests
HKGx Jan 27, 2022
48ccd5a
chore: actually fix coverage errors
HKGx Jan 27, 2022
083252b
chore: painstakingly change double quotes to single quotes
HKGx Jan 27, 2022
d9baefa
chore: some minor nitpicks and refactoring
RobertCraigie Jan 28, 2022
722352a
chore: fix lint error
RobertCraigie Jan 28, 2022
4b8947b
chore: fix mypy errors
RobertCraigie Jan 28, 2022
3df1f91
Merge branch 'main' into wip/fetch_binaries
RobertCraigie Jan 28, 2022
572ea77
chore: remove unused errors
HKGx Jan 29, 2022
92a5ea3
feat: improve InvalidBinaryVersion exception
HKGx Jan 29, 2022
789a6ce
Merge branch 'main' into wip/fetch_binaries
RobertCraigie Jan 30, 2022
130aee6
Merge branch 'main' into wip/fetch_binaries
RobertCraigie Feb 3, 2022
ade7552
Merge branch 'main' into wip/fetch_binaries
RobertCraigie Feb 17, 2022
2eea57f
chore: rename codecov target
RobertCraigie Feb 17, 2022
21f3da8
Merge branch 'main' of https://github.com/RobertCraigie/prisma-client…
HKGx Apr 9, 2022
6f594bf
feat(AbstractHTTP): improve typing on download method
HKGx Apr 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pydantic>=1.8.0
click>=7.1.2
python-dotenv>=0.12.0
typing-extensions>=3.7
distro>=1.6
cached-property; python_version < '3.8'
2 changes: 1 addition & 1 deletion scripts/docs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re
from pathlib import Path
from prisma.binaries.constants import PRISMA_VERSION
from prisma.binaries import PRISMA_VERSION


ROOTDIR = Path(__file__).parent.parent
Expand Down
7 changes: 6 additions & 1 deletion src/prisma/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ def validator(
if TYPE_CHECKING:
cached_property = property
else:
from cached_property import cached_property as cached_property
try:
from cached_property import cached_property as cached_property
except ImportError:
# If we can't import cached_property, fallback to the standard property
cached_property = property
else:

from functools import cached_property as cached_property
3 changes: 1 addition & 2 deletions src/prisma/binaries/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-

from .engine import *

from .binaries import *
from .constants import *
230 changes: 197 additions & 33 deletions src/prisma/binaries/binaries.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,229 @@
# -*- coding: utf-8 -*-

import logging
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Optional, List
import time
from typing import Callable, List, Optional, Tuple

import click
from pydantic import BaseSettings, Field
from prisma.errors import PrismaError

from .binary import Binary
from .engine import Engine
from .constants import GLOBAL_TEMP_DIR, PRISMA_CLI_NAME
from prisma.utils import time_since


__all__ = (
'ENGINES',
'BINARIES',
'ensure_cached',
'remove_all',
from . import platform
from .download import download

# Get system information
OS_SETTINGS: platform.OsSettings = platform.get_os_settings()
PLATFORM: str = platform.resolve_platform(OS_SETTINGS)
CLI_PLATFORM: str = OS_SETTINGS.system

# TODO: if this version changes but the engine version
# doesn't change then the CLI is incorrectly cached
# hardcoded CLI version version
PRISMA_VERSION: str = '3.8.1'

# versions can be found under https://github.com/prisma/prisma-engine/commits/main
ENGINE_VERSION = os.environ.get(
'PRISMA_ENGINE_VERSION', '34df67547cf5598f5a6cd3eb45f14ee70c3fb86f'
)
GLOBAL_TEMP_DIR: Path = (
Path(tempfile.gettempdir()) / 'prisma' / 'binaries' / 'engines' / ENGINE_VERSION
)
PLATFORM_EXE_EXTENSION: str = '.exe' if OS_SETTINGS.is_windows() else ''

PRISMA_CLI_NAME = f'prisma-cli-{PRISMA_VERSION}-{CLI_PLATFORM}{PLATFORM_EXE_EXTENSION}'


def default_prisma_cli_path() -> Path:
return GLOBAL_TEMP_DIR / PRISMA_CLI_NAME


def default_engine_path(name: str) -> Callable[[], Path]:
return lambda: (GLOBAL_TEMP_DIR / name).with_suffix(PLATFORM_EXE_EXTENSION)


class PrismaSettings(BaseSettings):
PRISMA_CLI_MIRROR: str = 'https://prisma-photongo.s3-eu-west-1.amazonaws.com'
PRISMA_ENGINES_MIRROR: str = 'https://binaries.prisma.sh'

PRISMA_QUERY_ENGINE_BINARY: Path = Field(
default_factory=default_engine_path('query-engine')
)
PRISMA_MIGRATION_ENGINE_BINARY: Path = Field(
default_factory=default_engine_path('migration-engine')
)
PRISMA_INTROSPECTION_ENGINE_BINARY: Path = Field(
default_factory=default_engine_path('introspection-engine')
)
PRISMA_CLI_BINARY: Path = Field(default_factory=default_prisma_cli_path)
PRISMA_FMT_BINARY: Path = Field(default_factory=default_engine_path('prisma-fmt'))
PRISMA_CLI_BINARY_TARGETS: List[str] = Field(default_factory=list)

def engine_url(self, name: str) -> str:
return (
f'{self.PRISMA_ENGINES_MIRROR}/'
'all_commits/'
f'{ENGINE_VERSION}/'
f'{PLATFORM}/'
f'{name}{PLATFORM_EXE_EXTENSION}.gz'
)

def prisma_cli_url(self) -> str:
return f'{self.PRISMA_CLI_MIRROR}/{PRISMA_CLI_NAME}.gz'


SETTINGS: PrismaSettings = PrismaSettings()


log: logging.Logger = logging.getLogger(__name__)

ENGINES = [
Engine(name='query-engine', env='PRISMA_QUERY_ENGINE_BINARY'),
Engine(name='migration-engine', env='PRISMA_MIGRATION_ENGINE_BINARY'),
Engine(name='introspection-engine', env='PRISMA_INTROSPECTION_ENGINE_BINARY'),
Engine(name='prisma-fmt', env='PRISMA_FMT_BINARY'),

class InvalidBinaryVersion(PrismaError):
pass


class Binary:
name: str
url: str
path: Path
settings: PrismaSettings

def __init__(
self,
name: str,
path: Path,
*,
url: Optional[str] = None,
settings: Optional[PrismaSettings] = None,
):
if settings is None:
settings = SETTINGS
self.settings = settings
self.name = name
self.path = path
self.url = settings.engine_url(name) if url is None else url

def download(self) -> None:
download(self.url, self.path)

def remove(self) -> None:
# This might fail if file is still in use, which happens during tests (somehow)!
try:
self.path.unlink()
except FileNotFoundError:
pass

def ensure_binary(self) -> None:
"""
If binary doesn't exist or is not a file, raise an error

Args:
binary (Binary): a binary to check
"""
if not self.path.exists():
raise FileNotFoundError(
f'{self.name} binary not found at {self.path}\nTry running `prisma fetch`'
)
if not self.path.is_file():
raise IsADirectoryError(f'{self.name} binary is a directory')
# Run binary with --version to check if it's the right version and capture stdout
start_version = time.monotonic()
process = subprocess.run(
[str(self.path), '--version'], stdout=subprocess.PIPE, check=True
)
log.debug('Version check took %s', time_since(start_version))

# Version format is: "<name-splitted-with-dashes> <version>"
# e.g. migration-engine-cli 34df67547cf5598f5a6cd3eb45f14ee70c3fb86f
# or query-engine 34df67547cf5598f5a6cd3eb45f14ee70c3fb86f
version = process.stdout.decode().split(' ')[1]
if version != ENGINE_VERSION:
raise InvalidBinaryVersion(
f'{self.name} binary version {version} is not {ENGINE_VERSION}'
)


EnginesType = Tuple[
Binary, # query-engine
Binary, # migration-engine
Binary, # introspection-engine
Binary, # prisma-fmt
]

BINARIES: List[Binary] = [
*ENGINES,
Binary(name=PRISMA_CLI_NAME, env='PRISMA_CLI_BINARY'),
# Maybe we should use a dict instead of a tuple? or a namedtuple?
BinariesType = Tuple[
Binary, # query-engine
Binary, # migration-engine
Binary, # introspection-engine
Binary, # prisma-fmt
Binary, # prisma-cli
]


def engines_from_settings(
settings: PrismaSettings,
) -> EnginesType:
return (
Binary(name='query-engine', path=settings.PRISMA_QUERY_ENGINE_BINARY),
Binary(name='migration-engine', path=settings.PRISMA_MIGRATION_ENGINE_BINARY),
Binary(
name='introspection-engine',
path=settings.PRISMA_INTROSPECTION_ENGINE_BINARY,
),
Binary(name='prisma-fmt', path=settings.PRISMA_FMT_BINARY),
)


def binaries_from_settings(
settings: PrismaSettings, *, engines: Optional[EnginesType] = None
) -> BinariesType:
if engines is None:
engines = engines_from_settings(settings)
return (
*engines,
Binary(
name='prisma-cli',
path=settings.PRISMA_CLI_BINARY,
url=settings.prisma_cli_url(),
),
)


ENGINES: EnginesType = engines_from_settings(SETTINGS)

BINARIES: BinariesType = binaries_from_settings(SETTINGS, engines=ENGINES)


def ensure_cached() -> Path:
binaries: List[Binary] = []
to_download: List[Binary] = []
for binary in BINARIES:
path = binary.path
if path.exists():
log.debug('%s cached at %s', binary.name, path)
else:
log.debug('%s not cached at %s', binary.name, path)
binaries.append(binary)

if not binaries:
log.debug('All binaries are cached')
if binary.path.exists():
log.debug('%s is cached, skipping download', binary.name)
continue
log.debug('%s is not cached, will download', binary.name)
to_download.append(binary)

if len(to_download) == 0:
log.debug('All binaries are cached, skipping download')
return GLOBAL_TEMP_DIR

def show_item(item: Optional[Binary]) -> str:
if item is not None:
return binary.name
return ''
return '' if item is None else item.name

with click.progressbar(
binaries,
to_download,
label='Downloading binaries',
fill_char=click.style('#', fg='yellow'),
item_show_func=show_item,
) as iterator:
for binary in iterator:
log.debug('Downloading %s from %s', binary.name, binary.url)
binary.download()

return GLOBAL_TEMP_DIR
Expand All @@ -67,5 +232,4 @@ def show_item(item: Optional[Binary]) -> str:
def remove_all() -> None:
"""Remove all downloaded binaries"""
for binary in BINARIES:
if binary.path.exists():
binary.path.unlink()
binary.remove()
46 changes: 0 additions & 46 deletions src/prisma/binaries/binary.py

This file was deleted.

48 changes: 0 additions & 48 deletions src/prisma/binaries/constants.py

This file was deleted.

Loading