Skip to content

Commit

Permalink
Vendor Python packages (take 2)
Browse files Browse the repository at this point in the history
This PR vendors all Python packages using the [vendoring](https://pypi.org/project/vendoring/) library.

Vendoring allows users to install packages that rely on versions of libraries that are in conflict with Cog's dependencies (e.g. Pydantic, see replicate#1562, replicate#1384, replicate#1186, replicate#1586, replicate#1336, replicate#785).

This PR is broken up into two commits: First one adds all the files in the `_vendor` directory, second one is the actually changed code.

Closes replicate#409

Signed-off-by: andreasjansson <[email protected]>
  • Loading branch information
andreasjansson committed Apr 15, 2024
1 parent 72398c7 commit f476a24
Show file tree
Hide file tree
Showing 29 changed files with 290 additions and 54 deletions.
20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,26 @@ To publish a prerelease version, append a [SemVer prerelease identifer](https://
git tag -a v0.1.0-alpha -m "Prerelease v0.1.0"
git push --tags

## Vendoring Python packages

We vendor the few Python libraries we depend on to avoid dependency hell. We use [vendoring](https://pypi.org/project/vendoring/), the same tool used by pip.

The vendored packages are defined in `python/cog/_vendor/vendor.txt`. If you add/change anything in there, run this to update the vendored libraries:

pip install vendoring
vendoring sync -v .

If you run into issues you may need to manually add a patch. Patches are stored in `python/tools/vendoring/patches`.

To create a new patch, copy the file you want to patch to `$FILENAME.new`, and edit that file as needed. For example:

cp python/cog/_vendor/curio/__main__.py python/cog/_vendor/curio/__main__.py.new

Then `diff` the new and old version to a patch file in `python/tools/vendoring/patches/$PACKAGE.patch` and re-run vendoring. For example:

diff -u ./python/cog/_vendor/curio/__main__.py ./python/cog/_vendor/curio/__main__.py.new >> python/tools/vendoring/patches/curio.patch
vendoring sync -v .

## Troubleshooting

### `cog command not found`
Expand Down
42 changes: 33 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,7 @@ urls."Source" = "https://github.com/replicate/cog"

requires-python = ">=3.7"
dependencies = [
# intentionally loose. perhaps these should be vendored to not collide with user code?
"attrs>=20.1,<24",
"fastapi>=0.75.2,<0.99.0",
"pydantic>=1.9,<2",
"PyYAML",
"requests>=2,<3",
"structlog>=20,<25",
'typing-compat; python_version < "3.8"',
"typing_extensions>=4.4.0",
"uvicorn[standard]>=0.12,<1",
]

optional-dependencies = { "dev" = [
Expand Down Expand Up @@ -111,3 +102,36 @@ extend-exclude = [
"S607", # Starting a process with a partial executable path"
"ANN",
]

[tool.black]
exclude = '(\.eggs|\.git|\.hg|\.mypy|_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist|_vendor)'

[tool.vendoring]
destination = "python/cog/_vendor/"
requirements = "python/cog/_vendor/vendor.txt"
namespace = "cog._vendor"

protected-files = ["__init__.py", "README.rst", "vendor.txt"]
patches-dir = "python/tools/vendoring/patches"

[tool.vendoring.transformations]
substitute = []
drop = [
# pyyaml falls back to plain python
"_yaml.*.so",

# sniffio ships with tests
"sniffio/_tests",

# h11 ships with tests
"h11/tests",

# pydantic hypothesis plugin, fall back to python
"pydantic/_hypothesis_plugin.*.so",
]

[tool.vendoring.typing-stubs]

[tool.vendoring.license.directories]

[tool.vendoring.license.fallback-urls]
2 changes: 1 addition & 1 deletion python/cog/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from ._vendor.pydantic import BaseModel

from .predictor import BasePredictor
from .types import ConcatenateIterator, File, Input, Path, Secret
Expand Down
2 changes: 1 addition & 1 deletion python/cog/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from urllib.parse import urlparse

import requests
from ._vendor import requests


def upload_file(fh: io.IOBase, output_file_prefix: str = None) -> str:
Expand Down
2 changes: 1 addition & 1 deletion python/cog/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from types import GeneratorType
from typing import Any, Callable

from pydantic import BaseModel
from ._vendor.pydantic import BaseModel

from .types import Path

Expand Down
4 changes: 2 additions & 2 deletions python/cog/logging.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging
import os

import structlog
from structlog.typing import EventDict
from ._vendor import structlog
from ._vendor.structlog.typing import EventDict


def replace_level_with_severity(
Expand Down
10 changes: 5 additions & 5 deletions python/cog/predictor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
)
from unittest.mock import patch

import structlog
from ._vendor import structlog

import cog.code_xforms as code_xforms

Expand All @@ -30,12 +30,12 @@
except ImportError: # Python < 3.8
from typing_compat import get_args, get_origin # type: ignore

import yaml
from pydantic import BaseModel, Field, create_model
from pydantic.fields import FieldInfo
from ._vendor import yaml
from ._vendor.pydantic import BaseModel, Field, create_model
from ._vendor.pydantic.fields import FieldInfo

# Added in Python 3.9. Can be from typing if we drop support for <3.9
from typing_extensions import Annotated
from ._vendor.typing_extensions import Annotated

from .errors import ConfigDoesNotExist, PredictorNotSet
from .types import (
Expand Down
2 changes: 1 addition & 1 deletion python/cog/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from enum import Enum
from types import ModuleType

import pydantic
from ._vendor import pydantic

BUNDLED_SCHEMA_PATH = ".cog/schema.py"

Expand Down
2 changes: 1 addition & 1 deletion python/cog/server/eventtypes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict

from attrs import define, field, validators
from .._vendor.attrs import define, field, validators


# From worker parent process
Expand Down
18 changes: 9 additions & 9 deletions python/cog/server/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
if TYPE_CHECKING:
from typing import ParamSpec

import attrs
import structlog
import uvicorn
from fastapi import Body, FastAPI, Header, HTTPException, Path, Response
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import ValidationError
from pydantic.error_wrappers import ErrorWrapper
from .._vendor import attrs
from .._vendor import structlog
from .._vendor import uvicorn
from .._vendor.fastapi import Body, FastAPI, Header, HTTPException, Path, Response
from .._vendor.fastapi.encoders import jsonable_encoder
from .._vendor.fastapi.exceptions import RequestValidationError
from .._vendor.fastapi.responses import JSONResponse
from .._vendor.pydantic import ValidationError
from .._vendor.pydantic.error_wrappers import ErrorWrapper

from .. import schema
from ..errors import PredictorNotSet
Expand Down
12 changes: 6 additions & 6 deletions python/cog/server/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
from multiprocessing.pool import AsyncResult, ThreadPool
from typing import Any, Callable, Optional, Tuple, Union, cast

import requests
import structlog
from attrs import define
from fastapi.encoders import jsonable_encoder
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry # type: ignore
from .._vendor import requests
from .._vendor import structlog
from .._vendor.attrs import define
from .._vendor.fastapi.encoders import jsonable_encoder
from .._vendor.requests.adapters import HTTPAdapter
from .._vendor.urllib3.util.retry import Retry # type: ignore

from .. import schema, types
from ..files import put_file_to_signed_endpoint
Expand Down
8 changes: 4 additions & 4 deletions python/cog/server/webhook.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import os
from typing import Any, Callable, Set

import requests
import structlog
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry # type: ignore
from .._vendor import requests
from .._vendor import structlog
from .._vendor.requests.adapters import HTTPAdapter
from .._vendor.urllib3.util.retry import Retry # type: ignore

from ..schema import Status, WebhookEvent
from .response_throttler import ResponseThrottler
Expand Down
4 changes: 2 additions & 2 deletions python/cog/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import urllib.request
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union

import requests
from pydantic import Field, SecretStr
from ._vendor import requests
from ._vendor.pydantic import Field, SecretStr

FILENAME_ILLEGAL_CHARS = set("\u0000/")

Expand Down
4 changes: 2 additions & 2 deletions python/tests/server/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from unittest import mock

import pytest
from attrs import define
from cog._vendor.attrs import define
from cog.command import ast_openapi_schema
from cog.server.http import create_app
from fastapi.testclient import TestClient
from cog._vendor.fastapi.testclient import TestClient


@define
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/fixtures/complex_output.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from cog import BaseModel


class Output(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/fixtures/input_unsupported_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from cog import BasePredictor
from pydantic import BaseModel
from cog import BaseModel


class Input(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/fixtures/openapi_custom_output_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from cog import BasePredictor
from pydantic import BaseModel
from cog import BaseModel


# Calling this `MyOutput` to test if cog renames it to `Output` in the schema
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/fixtures/openapi_output_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from cog import BasePredictor
from pydantic import BaseModel
from cog import BaseModel


# An output object called `Output` needs to be special cased because pydantic tries to dedupe it with the internal `Output`
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/fixtures/output_complex.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import io

from cog import BasePredictor, File
from pydantic import BaseModel
from cog import BaseModel


class Output(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/fixtures/output_iterator_complex.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Iterator, List

from cog import BasePredictor
from pydantic import BaseModel
from cog import BaseModel


class Output(BaseModel):
Expand Down
2 changes: 2 additions & 0 deletions python/tests/server/test_http_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from .conftest import uses_predictor

responses.mock.target = "cog._vendor.requests.adapters.HTTPAdapter.send"


@uses_predictor("input_none")
def test_no_input(client, match):
Expand Down
2 changes: 2 additions & 0 deletions python/tests/server/test_http_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from .conftest import uses_predictor, uses_predictor_with_client_options

responses.mock.target = "cog._vendor.requests.adapters.HTTPAdapter.send"


@uses_predictor("output_wrong_type")
def test_return_wrong_type(client):
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/test_webhook.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import requests
from cog._vendor import requests
import responses
from cog.schema import WebhookEvent
from cog.server.webhook import webhook_caller, webhook_caller_filtered
Expand Down
2 changes: 1 addition & 1 deletion python/tests/server/test_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any, Optional

import pytest
from attrs import define
from cog._vendor.attrs import define
from cog.server.eventtypes import (
Done,
Heartbeat,
Expand Down
2 changes: 1 addition & 1 deletion python/tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy as np
from cog.files import upload_file
from cog.json import make_encodeable, upload_files
from pydantic import BaseModel
from cog._vendor.pydantic import BaseModel


def test_make_encodeable_recursively_encodes_tuples():
Expand Down
Loading

0 comments on commit f476a24

Please sign in to comment.