Update package versions, workflows, LLMChain and Graph sorting (#1674)

* Update package versions in pyproject.toml and poetry.lock files

* Update poetry caching action and workflows

* Update poetry caching action and workflows

* Refactor LLMChainComponent build method in LLMChain.py

* Update poetry install command in Makefile

* Refactor Makefile to remove redundant install_backend targets

* Fix codespell issues in project

* Update package versions and dependencies

* Fix import order in chat_io.spec.ts, headerComponent/index.tsx, and chatMessage/index.tsx

* Update ruff command in Makefile and fix poetry cache reuse in Dockerfile

* Refactor ServiceManager class in manager.py to handle default service factories

* Fix typo in DOWNLOAD_WEBHOOK_URL variable assignment

* Refactor cache_service tests in test_cache_manager.py

* Add pytest-profiling

* Update Makefile to run unit tests with parallel execution

* Refactor ServiceManager class in manager.py to handle default service factories

* Refactor node_name condition in Graph class to use "Listen" instead of "GetNotified"

* Refactor file paths in tests/conftest.py for better readability and maintainability

* Sort vertices in each layer by dependency in Graph class

* Refactor variable declaration in SessionService class to use type hinting

* Refactor make tests command in python_test.yml workflow

* Refactor file paths in tests/conftest.py for better readability and maintainability

* Refactor imports in tests/conftest.py to include sqlmodel.Session and related dependencies

* Refactor file paths in tests/conftest.py to include available files in error message

* Refactor file paths in tests/conftest.py to include available files in error message

* Refactor file paths in tests/conftest.py to fix typo in BasicChatwithPromptAndHistory.json
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-04-11 12:20:56 -03:00 committed by GitHub
commit e582535bb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 396 additions and 286 deletions

View file

@ -0,0 +1,94 @@
# An action for setting up poetry install with caching.
# Using a custom action since the default action does not
# take poetry install groups into account.
# Action code from:
# https://github.com/actions/setup-python/issues/505#issuecomment-1273013236
# Copy of https://github.com/langchain-ai/langchain/blob/2f8dd1a1619f25daa4737df4d378b1acd6ff83c4/.github/actions/poetry_setup/action.yml
name: poetry-install-with-caching
description: Poetry install with support for caching of dependency groups.
inputs:
python-version:
description: Python version, supporting MAJOR.MINOR only
required: true
poetry-version:
description: Poetry version
required: true
cache-key:
description: Cache key to use for manual handling of caching
required: true
working-directory:
description: Directory whose poetry.lock file should be cached
required: true
runs:
using: composite
steps:
- uses: actions/setup-python@v5
name: Setup python ${{ inputs.python-version }}
id: setup-python
with:
python-version: ${{ inputs.python-version }}
- uses: actions/cache@v4
id: cache-bin-poetry
name: Cache Poetry binary - Python ${{ inputs.python-version }}
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "1"
with:
path: |
/opt/pipx/venvs/poetry
# This step caches the poetry installation, so make sure it's keyed on the poetry version as well.
key: bin-poetry-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-${{ inputs.poetry-version }}
- name: Refresh shell hashtable and fixup softlinks
if: steps.cache-bin-poetry.outputs.cache-hit == 'true'
shell: bash
env:
POETRY_VERSION: ${{ inputs.poetry-version }}
PYTHON_VERSION: ${{ inputs.python-version }}
run: |
set -eux
# Refresh the shell hashtable, to ensure correct `which` output.
hash -r
# `actions/cache@v3` doesn't always seem able to correctly unpack softlinks.
# Delete and recreate the softlinks pipx expects to have.
rm /opt/pipx/venvs/poetry/bin/python
cd /opt/pipx/venvs/poetry/bin
ln -s "$(which "python$PYTHON_VERSION")" python
chmod +x python
cd /opt/pipx_bin/
ln -s /opt/pipx/venvs/poetry/bin/poetry poetry
chmod +x poetry
# Ensure everything got set up correctly.
/opt/pipx/venvs/poetry/bin/python --version
/opt/pipx_bin/poetry --version
- name: Install poetry
if: steps.cache-bin-poetry.outputs.cache-hit != 'true'
shell: bash
env:
POETRY_VERSION: ${{ inputs.poetry-version }}
PYTHON_VERSION: ${{ inputs.python-version }}
# Install poetry using the python version installed by setup-python step.
run: pipx install "poetry==$POETRY_VERSION" --python '${{ steps.setup-python.outputs.python-path }}' --verbose
- name: Restore pip and poetry cached dependencies
uses: actions/cache@v4
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "4"
WORKDIR: ${{ inputs.working-directory == '' && '.' || inputs.working-directory }}
with:
path: |
~/.cache/pip
~/.cache/pypoetry/virtualenvs
~/.cache/pypoetry/cache
~/.cache/pypoetry/artifacts
${{ env.WORKDIR }}/.venv
key: py-deps-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}-${{ inputs.cache-key }}-${{ hashFiles(format('{0}/**/poetry.lock', env.WORKDIR)) }}

View file

@ -26,20 +26,24 @@ jobs:
- "3.11"
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: |
pipx install poetry==$POETRY_VERSION
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: setup-python
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_caching"
with:
python-version: ${{ matrix.python-version }}
cache: poetry
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: ${{ runner.os }}-poetry-${{ env.POETRY_VERSION }}-${{ hashFiles('**/poetry.lock') }}
- name: Install Python dependencies
run: |
poetry env use ${{ matrix.python-version }}
poetry install
if: ${{ steps.setup-python.outputs.cache-hit != 'true' }}
- name: Analysing the code with our lint
- name: Get .mypy_cache to speed up mypy
uses: actions/cache@v4
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2"
with:
path: |
./.mypy_cache
key: ${{ runner.os }}-mypy-${{ hashFiles('**/pyproject.toml') }}
- name: Lint check
run: |
make lint

View file

@ -29,19 +29,16 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry==$POETRY_VERSION
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: setup-python
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_caching"
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: ${{ runner.os }}-poetry-${{ env.POETRY_VERSION }}-${{ hashFiles('**/poetry.lock') }}
- name: Install Python dependencies
run: |
poetry env use ${{ matrix.python-version }}
poetry install
if: ${{ steps.setup-python.outputs.cache-hit != 'true' }}
- name: Run unit tests
run: |
make tests

3
.gitignore vendored
View file

@ -264,4 +264,5 @@ scratchpad*
chroma*/*
stuff/*
src/frontend/playwright-report/index.html
*.bak
*.bak
prof/*

View file

@ -8,6 +8,14 @@ env ?= .env
open_browser ?= true
path = src/backend/base/langflow/frontend
codespell:
@poetry install --with spelling
poetry run codespell --toml pyproject.toml
fix_codespell:
@poetry install --with spelling
poetry run codespell --toml pyproject.toml --write
setup_poetry:
pipx install poetry
@ -39,20 +47,16 @@ coverage:
# allow passing arguments to pytest
tests:
@make install_backend
poetry run pytest tests --instafail $(args)
# Use like:
format:
poetry run ruff . --fix
poetry run ruff check . --fix
poetry run ruff format .
cd src/frontend && npm run format
lint:
make install_backend
poetry run mypy --namespace-packages -p "langflow"
poetry run ruff . --fix
install_frontend:
cd src/frontend && npm install
@ -129,12 +133,12 @@ frontendc:
make run_frontend
install_backend:
@echo 'Setting up the environment'
@make setup_env
@echo 'Installing backend dependencies'
@poetry install --extras deploy
@poetry install
backend:
@echo 'Setting up the environment'
@make setup_env
make install_backend
@-kill -9 `lsof -t -i:7860`
ifdef login

View file

@ -57,7 +57,7 @@ RUN apt-get update \
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
# The --mount will mount the buildx cache directory to where
# Poetry and Pip store their cache so that they can re-use it
# Poetry and Pip store their cache so that they can reuse it
RUN --mount=type=cache,target=/root/.cache \
curl -sSL https://install.python-poetry.org | python3 -

View file

@ -56,7 +56,7 @@ RUN apt-get update \
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
# The --mount will mount the buildx cache directory to where
# Poetry and Pip store their cache so that they can re-use it
# Poetry and Pip store their cache so that they can reuse it
RUN --mount=type=cache,target=/root/.cache \
curl -sSL https://install.python-poetry.org | python3 -

98
poetry.lock generated
View file

@ -1130,6 +1130,23 @@ prompt-toolkit = ">=3.0.36"
[package.extras]
testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"]
[[package]]
name = "codespell"
version = "2.2.6"
description = "Codespell"
optional = false
python-versions = ">=3.8"
files = [
{file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"},
{file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"},
]
[package.extras]
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
hard-encoding-detection = ["chardet"]
toml = ["tomli"]
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
name = "cohere"
version = "5.2.5"
@ -2659,6 +2676,17 @@ files = [
httpx = ">=0.23,<0.28"
pydantic = ">=1.10,<3"
[[package]]
name = "gprof2dot"
version = "2022.7.29"
description = "Generate a dot graph from the output of several profilers."
optional = false
python-versions = ">=2.7"
files = [
{file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"},
{file = "gprof2dot-2022.7.29.tar.gz", hash = "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5"},
]
[[package]]
name = "greenlet"
version = "3.0.3"
@ -3930,13 +3958,13 @@ url = "src/backend/base"
[[package]]
name = "langfuse"
version = "2.23.2"
version = "2.24.0"
description = "A client library for accessing langfuse"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langfuse-2.23.2-py3-none-any.whl", hash = "sha256:591811d36fe42b26451615686ea91c04d96afd247ac023818b1ca72861e537a6"},
{file = "langfuse-2.23.2.tar.gz", hash = "sha256:11df6c6ff6ca5f6a079836f2530d5f7dcbe11d7058ae16efcafbf8275b0e14b6"},
{file = "langfuse-2.24.0-py3-none-any.whl", hash = "sha256:356e8260f093470f09ed8aabad769bae6e9eb1e8a8682f53f1d135c69e07d0d9"},
{file = "langfuse-2.24.0.tar.gz", hash = "sha256:c2f9f4c66578ea70da20c2f1bf6ac468b349471b9f2644f95add57aa00817827"},
]
[package.dependencies]
@ -3986,13 +4014,13 @@ regex = ["regex"]
[[package]]
name = "litellm"
version = "1.34.40"
version = "1.34.41"
description = "Library to easily interface with LLM API providers"
optional = false
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
files = [
{file = "litellm-1.34.40-py3-none-any.whl", hash = "sha256:ad6d321eec9819d1697223f5c528ca5c5f2476944bb2afc691cf8517804777e7"},
{file = "litellm-1.34.40.tar.gz", hash = "sha256:99faea82cc8bfa1ca6e3063adebfd9e65ecc60b00f4de9a47bda0559d6c9f637"},
{file = "litellm-1.34.41-py3-none-any.whl", hash = "sha256:ec6b1fb7f27178ad36f3be64e2f0fb73a637c0d7c128d5aa1d845414cc21be66"},
{file = "litellm-1.34.41.tar.gz", hash = "sha256:e8ad1a0b9cf4abea0471394db8c95f479e94e271bc1ce3d98204e1a6ed917e93"},
]
[package.dependencies]
@ -7185,6 +7213,25 @@ pytest = ">=6.2.5"
[package.extras]
dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
name = "pytest-profiling"
version = "1.7.0"
description = "Profiling plugin for py.test"
optional = false
python-versions = "*"
files = [
{file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"},
{file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"},
]
[package.dependencies]
gprof2dot = "*"
pytest = "*"
six = "*"
[package.extras]
tests = ["pytest-virtualenv"]
[[package]]
name = "pytest-sugar"
version = "1.0.0"
@ -7999,28 +8046,28 @@ msg-parse = ["extract-msg (>=0.27)"]
[[package]]
name = "ruff"
version = "0.2.2"
version = "0.3.5"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"},
{file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"},
{file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"},
{file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"},
{file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"},
{file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"},
{file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"},
{file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"},
{file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"},
{file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"},
{file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"},
{file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"},
{file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"},
{file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"},
{file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"},
{file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"},
]
[[package]]
@ -10256,11 +10303,10 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[extras]
all = []
deploy = ["celery", "flower", "redis"]
local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.12"
content-hash = "0d658da1ee75640f74d7335685760091df2a452e84eef81dcda2590dd4044e92"
content-hash = "9dd152b30031767c522c77e2ad5fc4597a8d1590b13968af143bd382e056b2a1"

View file

@ -85,10 +85,10 @@ langchain-openai = "^0.1.1"
[tool.poetry.group.dev.dependencies]
types-redis = "^4.6.0.5"
ipykernel = "^6.29.0"
mypy = "^1.8.0"
ruff = "^0.2.1"
mypy = "^1.9.0"
ruff = "^0.3.5"
httpx = "*"
pytest = "^8.0.0"
pytest = "^8.1.0"
types-requests = "^2.31.0"
requests = "^2.31.0"
pytest-cov = "^4.1.0"
@ -106,11 +106,23 @@ pytest-sugar = "^1.0.0"
respx = "^0.21.1"
pytest-instafail = "^0.5.0"
pytest-asyncio = "^0.23.0"
pytest-profiling = "^1.7.0"
[tool.poetry.extras]
deploy = ["celery", "redis", "flower"]
local = ["llama-cpp-python", "sentence-transformers", "ctransformers"]
all = ["deploy", "local"]
[tool.poetry.group.spelling]
optional = true
[tool.poetry.group.spelling.dependencies]
codespell = "^2.2.6"
[tool.codespell]
skip = '.git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,*.trig'
# Ignore latin etc
ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
[tool.pytest.ini_options]

View file

@ -5,6 +5,7 @@ Revises: 1a110b568907
Create Date: 2024-04-10 19:17:22.820455
"""
from typing import Sequence, Union
import sqlalchemy as sa

View file

@ -1,9 +1,10 @@
from typing import Optional
from langchain.chains import LLMChain
from langchain.chains.llm import LLMChain
from langflow.field_typing import BaseLanguageModel, BaseMemory, BasePromptTemplate, Text
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.prompts import PromptTemplate
class LLMChainComponent(CustomComponent):
@ -19,10 +20,11 @@ class LLMChainComponent(CustomComponent):
def build(
self,
prompt: BasePromptTemplate,
template: Text,
llm: BaseLanguageModel,
memory: Optional[BaseMemory] = None,
) -> Text:
prompt = PromptTemplate.from_template(template)
runnable = LLMChain(prompt=prompt, llm=llm, memory=memory)
result_dict = runnable.invoke({})
output_key = runnable.output_key

View file

@ -918,7 +918,7 @@ class Graph:
return ChatVertex
elif node_name in ["ShouldRunNext"]:
return RoutingVertex
elif node_name in ["SharedState", "Notify", "GetNotified"]:
elif node_name in ["SharedState", "Notify", "Listen"]:
return StateVertex
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_base_type]
@ -1130,6 +1130,34 @@ class Graph:
return vertices_layers
def sort_layer_by_dependency(self, vertices_layers: List[List[str]]) -> List[List[str]]:
"""Sorts the vertices in each layer by dependency, ensuring no vertex depends on a subsequent vertex."""
sorted_layers = []
for layer in vertices_layers:
sorted_layer = self._sort_single_layer_by_dependency(layer)
sorted_layers.append(sorted_layer)
return sorted_layers
def _sort_single_layer_by_dependency(self, layer: List[str]) -> List[str]:
"""Sorts a single layer by dependency using a stable sorting method."""
# Build a map of each vertex to its index in the layer for quick lookup.
index_map = {vertex: index for index, vertex in enumerate(layer)}
# Create a sorted copy of the layer based on dependency order.
sorted_layer = sorted(layer, key=lambda vertex: self._max_dependency_index(vertex, index_map), reverse=True)
return sorted_layer
def _max_dependency_index(self, vertex_id: str, index_map: Dict[str, int]) -> int:
"""Finds the highest index a given vertex's dependencies occupy in the same layer."""
vertex = self.get_vertex(vertex_id)
max_index = -1
for successor in vertex.successors: # Assuming vertex.successors is a list of successor vertex identifiers.
if successor.id in index_map:
max_index = max(max_index, index_map[successor.id])
return max_index
def sort_vertices(
self,
stop_component_id: Optional[str] = None,
@ -1151,6 +1179,9 @@ class Graph:
vertices_layers = self.layered_topological_sort(vertices)
vertices_layers = self.sort_by_avg_build_time(vertices_layers)
# vertices_layers = self.sort_chat_inputs_first(vertices_layers)
# Now we should sort each layer in a way that we make sure
# vertex V does not depend on vertex V+1
vertices_layers = self.sort_layer_by_dependency(vertices_layers)
self.increment_run_count()
self._sorted_vertices_layers = vertices_layers
first_layer = vertices_layers[0]

View file

@ -5,17 +5,6 @@ from loguru import logger
from langflow.graph import Graph
async def build_sorted_vertices(data_graph, flow_id: str) -> Tuple[Graph, Dict]:
"""
Build langchain object from data_graph.
"""
logger.debug("Building langchain object")
graph = Graph.from_payload(data_graph, flow_id=flow_id)
return graph, {}
def get_memory_key(langchain_object):
"""
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.

View file

@ -1,7 +1,7 @@
from contextlib import contextmanager
from typing import TYPE_CHECKING, Generator
from langflow.services import ServiceType, service_manager
from langflow.services.schema import ServiceType
if TYPE_CHECKING:
from sqlmodel import Session
@ -21,7 +21,7 @@ if TYPE_CHECKING:
from langflow.services.variable.service import VariableService
def get_service(service_type: ServiceType):
def get_service(service_type: ServiceType, default=None):
"""
Retrieves the service instance for the given service type.
@ -32,7 +32,13 @@ def get_service(service_type: ServiceType):
Any: The service instance.
"""
return service_manager.get(service_type) # type: ignore
from langflow.services.manager import service_manager
if not service_manager.factories:
#! This is a workaround to ensure that the service manager is initialized
#! Not optimal, but it works for now
service_manager.register_factories()
return service_manager.get(service_type, default) # type: ignore
def get_state_service() -> "StateService":
@ -42,7 +48,9 @@ def get_state_service() -> "StateService":
Returns:
The StateService instance.
"""
return service_manager.get(ServiceType.STATE_SERVICE) # type: ignore
from langflow.services.state.factory import StateServiceFactory
return get_service(ServiceType.STATE_SERVICE, StateServiceFactory()) # type: ignore
def get_socket_service() -> "SocketIOService":
@ -52,7 +60,7 @@ def get_socket_service() -> "SocketIOService":
Returns:
SocketIOService: The SocketIOService instance.
"""
return service_manager.get(ServiceType.SOCKETIO_SERVICE) # type: ignore
return get_service(ServiceType.SOCKETIO_SERVICE) # type: ignore
def get_storage_service() -> "StorageService":
@ -62,7 +70,9 @@ def get_storage_service() -> "StorageService":
Returns:
The storage service instance.
"""
return service_manager.get(ServiceType.STORAGE_SERVICE) # type: ignore
from langflow.services.storage.factory import StorageServiceFactory
return get_service(ServiceType.STORAGE_SERVICE, default=StorageServiceFactory()) # type: ignore
def get_variable_service() -> "VariableService":
@ -73,7 +83,9 @@ def get_variable_service() -> "VariableService":
The VariableService instance.
"""
return service_manager.get(ServiceType.VARIABLE_SERVICE) # type: ignore
from langflow.services.variable.factory import VariableServiceFactory
return get_service(ServiceType.VARIABLE_SERVICE, VariableServiceFactory()) # type: ignore
def get_plugins_service() -> "PluginService":
@ -83,7 +95,7 @@ def get_plugins_service() -> "PluginService":
Returns:
PluginService: The PluginService instance.
"""
return service_manager.get(ServiceType.PLUGIN_SERVICE) # type: ignore
return get_service(ServiceType.PLUGIN_SERVICE) # type: ignore
def get_settings_service() -> "SettingsService":
@ -98,14 +110,9 @@ def get_settings_service() -> "SettingsService":
Raises:
ValueError: If the service cannot be retrieved or initialized.
"""
try:
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
except ValueError:
# initialize settings service
from langflow.services.manager import initialize_settings_service
from langflow.services.settings.factory import SettingsServiceFactory
initialize_settings_service()
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
return get_service(ServiceType.SETTINGS_SERVICE, SettingsServiceFactory()) # type: ignore
def get_db_service() -> "DatabaseService":
@ -116,7 +123,9 @@ def get_db_service() -> "DatabaseService":
The DatabaseService instance.
"""
return service_manager.get(ServiceType.DATABASE_SERVICE) # type: ignore
from langflow.services.database.factory import DatabaseServiceFactory
return get_service(ServiceType.DATABASE_SERVICE, DatabaseServiceFactory()) # type: ignore
def get_session() -> Generator["Session", None, None]:
@ -165,7 +174,9 @@ def get_cache_service() -> "CacheService":
Returns:
The cache service instance.
"""
return service_manager.get(ServiceType.CACHE_SERVICE) # type: ignore
from langflow.services.cache.factory import CacheServiceFactory
return get_service(ServiceType.CACHE_SERVICE, CacheServiceFactory()) # type: ignore
def get_session_service() -> "SessionService":
@ -175,7 +186,9 @@ def get_session_service() -> "SessionService":
Returns:
The session service instance.
"""
return service_manager.get(ServiceType.SESSION_SERVICE) # type: ignore
from langflow.services.session.factory import SessionServiceFactory
return get_service(ServiceType.SESSION_SERVICE, SessionServiceFactory()) # type: ignore
def get_monitor_service() -> "MonitorService":
@ -185,7 +198,9 @@ def get_monitor_service() -> "MonitorService":
Returns:
MonitorService: The MonitorService instance.
"""
return service_manager.get(ServiceType.MONITOR_SERVICE) # type: ignore
from langflow.services.monitor.factory import MonitorServiceFactory
return get_service(ServiceType.MONITOR_SERVICE, MonitorServiceFactory()) # type: ignore
def get_task_service() -> "TaskService":
@ -196,7 +211,9 @@ def get_task_service() -> "TaskService":
The TaskService instance.
"""
return service_manager.get(ServiceType.TASK_SERVICE) # type: ignore
from langflow.services.task.factory import TaskServiceFactory
return get_service(ServiceType.TASK_SERVICE, TaskServiceFactory()) # type: ignore
def get_chat_service() -> "ChatService":
@ -206,7 +223,7 @@ def get_chat_service() -> "ChatService":
Returns:
ChatService: The chat service instance.
"""
return service_manager.get(ServiceType.CHAT_SERVICE) # type: ignore
return get_service(ServiceType.CHAT_SERVICE) # type: ignore
def get_store_service() -> "StoreService":
@ -216,4 +233,4 @@ def get_store_service() -> "StoreService":
Returns:
StoreService: The StoreService instance.
"""
return service_manager.get(ServiceType.STORE_SERVICE) # type: ignore
return get_service(ServiceType.STORE_SERVICE) # type: ignore

View file

@ -1,13 +1,20 @@
from typing import TYPE_CHECKING, Dict
import importlib
import inspect
from typing import TYPE_CHECKING, Dict, Optional
from loguru import logger
if TYPE_CHECKING:
from langflow.services.base import Service
from langflow.services.factory import ServiceFactory
from langflow.services.schema import ServiceType
class NoFactoryRegisteredError(Exception):
pass
class ServiceManager:
"""
Manages the creation of different services.
@ -16,6 +23,15 @@ class ServiceManager:
def __init__(self):
self.services: Dict[str, "Service"] = {}
self.factories = {}
self.register_factories()
def register_factories(self):
for factory in self.get_factories():
try:
self.register_factory(factory)
except Exception as exc:
logger.exception(exc)
logger.error(f"Error initializing {factory}: {exc}")
def register_factory(
self,
@ -28,24 +44,28 @@ class ServiceManager:
service_name = service_factory.service_class.name
self.factories[service_name] = service_factory
def get(self, service_name: "ServiceType") -> "Service":
def get(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None) -> "Service":
"""
Get (or create) a service by its name.
"""
if service_name not in self.services:
self._create_service(service_name)
self._create_service(service_name, default)
return self.services[service_name]
def _create_service(self, service_name: "ServiceType"):
def _create_service(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None):
"""
Create a new service given its name, handling dependencies.
"""
logger.debug(f"Create service {service_name}")
self._validate_service_creation(service_name)
self._validate_service_creation(service_name, default)
# Create dependencies first
factory = self.factories.get(service_name)
if factory is None and default is not None:
self.register_factory(default)
factory = default
for dependency in factory.dependencies:
if dependency not in self.services:
self._create_service(dependency)
@ -57,12 +77,12 @@ class ServiceManager:
self.services[service_name] = self.factories[service_name].create(**dependent_services)
self.services[service_name].set_ready()
def _validate_service_creation(self, service_name: "ServiceType"):
def _validate_service_creation(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None):
"""
Validate whether the service can be created.
"""
if service_name not in self.factories:
raise ValueError(f"No factory registered for the service class '{service_name.name}'")
if service_name not in self.factories and default is None:
raise NoFactoryRegisteredError(f"No factory registered for the service class '{service_name.name}'")
def update(self, service_name: "ServiceType"):
"""
@ -88,6 +108,34 @@ class ServiceManager:
self.services = {}
self.factories = {}
@staticmethod
def get_factories():
from langflow.services.factory import ServiceFactory
from langflow.services.schema import ServiceType
service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
base_module = "langflow.services"
factories = []
for name in service_names:
try:
module_name = f"{base_module}.{name}.factory"
module = importlib.import_module(module_name)
# Find all classes in the module that are subclasses of ServiceFactory
for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
factories.append(obj())
break
except Exception as exc:
logger.exception(exc)
raise RuntimeError(
f"Could not initialize services. Please check your settings. Error in {name}."
) from exc
return factories
service_manager = ServiceManager()
@ -106,9 +154,7 @@ def initialize_session_service():
Initialize the session manager.
"""
from langflow.services.cache import factory as cache_factory
from langflow.services.session import (
factory as session_service_factory,
) # type: ignore
from langflow.services.session import factory as session_service_factory # type: ignore
initialize_settings_service()

View file

@ -19,5 +19,5 @@ class ServiceType(str, Enum):
VARIABLE_SERVICE = "variable_service"
STORAGE_SERVICE = "storage_service"
MONITOR_SERVICE = "monitor_service"
SOCKETIO_SERVICE = "socket_service"
# SOCKETIO_SERVICE = "socket_service"
STATE_SERVICE = "state_service"

View file

@ -1,6 +1,5 @@
from typing import Coroutine, Optional
from langflow.interface.run import build_sorted_vertices
from langflow.services.base import Service
from langflow.services.cache.base import CacheService
from langflow.services.session.utils import compute_dict_hash, session_id_generator
@ -25,8 +24,10 @@ class SessionService(Service):
if data_graph is None:
return (None, None)
# If not cached, build the graph and cache it
graph, artifacts = await build_sorted_vertices(data_graph, flow_id)
from langflow.graph.graph.base import Graph
graph = Graph.from_payload(data_graph, flow_id=flow_id)
artifacts: dict = {}
await self.cache_service.set(key, (graph, artifacts))
return graph, artifacts

View file

@ -100,9 +100,9 @@ class Settings(BaseSettings):
STORE: Optional[bool] = True
STORE_URL: Optional[str] = "https://api.langflow.store"
DOWNLOAD_WEBHOOK_URL: Optional[
str
] = "https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
DOWNLOAD_WEBHOOK_URL: Optional[str] = (
"https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
)
LIKE_WEBHOOK_URL: Optional[str] = "https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
STORAGE_TYPE: str = "local"

View file

@ -1,41 +1,13 @@
import importlib
import inspect
from loguru import logger
from sqlmodel import Session, select
from langflow.services.auth.utils import create_super_user, verify_password
from langflow.services.cache.factory import CacheServiceFactory
from langflow.services.database.utils import initialize_database
from langflow.services.factory import ServiceFactory
from langflow.services.manager import service_manager
from langflow.services.schema import ServiceType
from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from langflow.services.socket.utils import set_socketio_server
from .deps import get_db_service, get_session, get_settings_service
def get_factories():
service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
base_module = "langflow.services"
factories = []
for name in service_names:
try:
module_name = f"{base_module}.{name}.factory"
module = importlib.import_module(module_name)
# Find all classes in the module that are subclasses of ServiceFactory
for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
factories.append(obj())
break
except Exception as exc:
logger.exception(exc)
raise RuntimeError(f"Could not initialize services. Please check your settings. Error in {name}.") from exc
return factories
from .deps import get_db_service, get_service, get_session, get_settings_service
def get_or_create_super_user(session: Session, username, password, is_default):
@ -145,6 +117,8 @@ def teardown_services():
except Exception as exc:
logger.exception(exc)
try:
from langflow.services.manager import service_manager
service_manager.teardown()
except Exception as exc:
logger.exception(exc)
@ -156,7 +130,7 @@ def initialize_settings_service():
"""
from langflow.services.settings import factory as settings_factory
service_manager.register_factory(settings_factory.SettingsServiceFactory())
get_service(ServiceType.SETTINGS_SERVICE, settings_factory.SettingsServiceFactory())
def initialize_session_service():
@ -168,11 +142,13 @@ def initialize_session_service():
initialize_settings_service()
service_manager.register_factory(
get_service(
ServiceType.CACHE_SERVICE,
cache_factory.CacheServiceFactory(),
)
service_manager.register_factory(
get_service(
ServiceType.SESSION_SERVICE,
session_service_factory.SessionServiceFactory(),
)
@ -181,27 +157,17 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
"""
Initialize all the services needed.
"""
for factory in get_factories():
try:
service_manager.register_factory(factory)
except Exception as exc:
logger.exception(exc)
logger.error(f"Error initializing {factory}: {exc}")
# Test cache connection
service_manager.get(ServiceType.CACHE_SERVICE)
get_service(ServiceType.CACHE_SERVICE, default=CacheServiceFactory())
# Setup the superuser
try:
initialize_database(fix_migration=fix_migration)
except Exception as exc:
logger.error(exc)
raise exc
setup_superuser(service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session()))
setup_superuser(get_service(ServiceType.SETTINGS_SERVICE), next(get_session()))
try:
get_db_service().migrate_flows_if_auto_login()
except Exception as exc:
logger.error(f"Error migrating flows: {exc}")
raise RuntimeError("Error migrating flows") from exc
# Initialize the SocketIO service
set_socketio_server(socketio_server)

View file

@ -62,30 +62,6 @@ cryptography = "^42.0.5"
asyncer = "^0.0.5"
[tool.poetry.group.dev.dependencies]
pytest-asyncio = "^0.23.1"
types-redis = "^4.6.0.5"
ipykernel = "^6.26.0"
mypy = "^1.7.1"
ruff = "^0.3.5"
httpx = "*"
pytest = "^8.1.1"
types-requests = "^2.31.0"
requests = "^2.31.0"
pytest-cov = "^5.0.0"
pandas-stubs = "^2.2.1.230412"
types-pillow = "^10.2.0.0"
types-pyyaml = "^6.0.12.8"
types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13"
locust = "^2.24.1"
pytest-mock = "^3.14.0"
pytest-xdist = "^3.5.0"
types-pywin32 = "^306.0.0.4"
types-google-cloud-ndb = "^2.3.0.0"
pytest-sugar = "^1.0.0"
[tool.poetry.extras]
deploy = ["celery", "redis", "flower"]
local = ["llama-cpp-python", "sentence-transformers", "ctransformers"]

View file

@ -10,6 +10,7 @@ import orjson
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
from sqlmodel import Session, SQLModel, create_engine, select
from langflow.graph.graph.base import Graph
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_password_hash
@ -27,25 +28,39 @@ if TYPE_CHECKING:
def pytest_configure():
pytest.BASIC_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "basic_example.json"
pytest.COMPLEX_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_example.json"
pytest.COMPLEX_DEPS_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_deps_example.json"
pytest.OPENAPI_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "Openapi.json"
pytest.GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "grouped_chat.json"
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "one_group_chat.json"
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "vector_store_grouped.json"
data_path = Path(__file__).parent.absolute() / "data"
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = (
Path(__file__).parent.absolute() / "data" / "BasicChatWithPromptAndHistory.json"
)
pytest.CHAT_INPUT = Path(__file__).parent.absolute() / "data" / "ChatInputTest.json"
pytest.TWO_OUTPUTS = Path(__file__).parent.absolute() / "data" / "TwoOutputsTest.json"
pytest.VECTOR_STORE_PATH = Path(__file__).parent.absolute() / "data" / "Vector_store.json"
pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json"
pytest.COMPLEX_EXAMPLE_PATH = data_path / "complex_example.json"
pytest.OPENAPI_EXAMPLE_PATH = data_path / "Openapi.json"
pytest.GROUPED_CHAT_EXAMPLE_PATH = data_path / "grouped_chat.json"
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = data_path / "one_group_chat.json"
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = data_path / "vector_store_grouped.json"
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = data_path / "BasicChatwithPromptandHistory.json"
pytest.CHAT_INPUT = data_path / "ChatInputTest.json"
pytest.TWO_OUTPUTS = data_path / "TwoOutputsTest.json"
pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json"
pytest.CODE_WITH_SYNTAX_ERROR = """
def get_text():
retun "Hello World"
"""
# validate that all the paths are correct and the files exist
for path in [
pytest.BASIC_EXAMPLE_PATH,
pytest.COMPLEX_EXAMPLE_PATH,
pytest.OPENAPI_EXAMPLE_PATH,
pytest.GROUPED_CHAT_EXAMPLE_PATH,
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH,
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH,
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY,
pytest.CHAT_INPUT,
pytest.TWO_OUTPUTS,
pytest.VECTOR_STORE_PATH,
]:
assert path.exists(), f"File {path} does not exist. Available files: {list(data_path.iterdir())}"
@pytest.fixture(autouse=True)
def check_openai_api_key_in_environment_variables():
@ -192,16 +207,6 @@ def json_vector_store():
return f.read()
@pytest.fixture
def complex_graph_with_groups():
with open(pytest.COMPLEX_DEPS_EXAMPLE_PATH, "r") as f:
flow_graph = json.load(f)
data_graph = flow_graph["data"]
nodes = data_graph["nodes"]
edges = data_graph["edges"]
return Graph(nodes, edges)
@pytest.fixture(name="client", autouse=True)
def client_fixture(session: Session, monkeypatch, request):
# Set the database url to a test database

View file

@ -1,82 +0,0 @@
from io import StringIO
import pandas as pd
import pytest
from langflow.services.chat.cache import CacheService
from PIL import Image
@pytest.fixture
def cache_service():
return CacheService()
def test_cache_service_attach_detach_notify(cache_service):
observer_called = False
def observer():
nonlocal observer_called
observer_called = True
cache_service.attach(observer)
cache_service.notify()
assert observer_called
observer_called = False
cache_service.detach(observer)
cache_service.notify()
assert not observer_called
def test_cache_service_client_context(cache_service):
with cache_service.set_client_id("client1"):
cache_service.add("foo", "bar", "string")
assert cache_service.get("foo") == {
"obj": "bar",
"type": "string",
"extension": "str",
}
with cache_service.set_client_id("client2"):
cache_service.add("baz", "qux", "string")
assert cache_service.get("baz") == {
"obj": "qux",
"type": "string",
"extension": "str",
}
with pytest.raises(KeyError):
cache_service.get("foo")
def test_cache_service_add_pandas(cache_service):
df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
with cache_service.set_client_id("client1"):
cache_service.add_pandas("test_df", df)
cached_df = cache_service.get("test_df")
assert cached_df["type"] == "pandas"
assert cached_df["extension"] == "csv"
read_df = pd.read_csv(StringIO(cached_df["obj"]), index_col=0)
pd.testing.assert_frame_equal(df, read_df)
def test_cache_service_add_image(cache_service):
img = Image.new("RGB", (50, 50), color="red")
with cache_service.set_client_id("client1"):
cache_service.add_image("test_image", img)
cached_img = cache_service.get("test_image")
assert cached_img["type"] == "image"
assert cached_img["extension"] == "png"
assert isinstance(cached_img["obj"], Image.Image)
def test_cache_service_get_last(cache_service):
with cache_service.set_client_id("client1"):
cache_service.add("foo", "bar", "string")
cache_service.add("baz", "qux", "string")
last_item = cache_service.get_last()
assert last_item == {"obj": "qux", "type": "string", "extension": "str"}

View file

@ -514,7 +514,7 @@ def test_successful_run_with_output_type_any(client, starter_project, created_ap
def test_successful_run_with_output_type_debug(client, starter_project, created_api_key):
# This one should return outputs for all components
# Let's just check the amount of outputs(there hsould be 7)
# Let's just check the amount of outputs(there should be 7)
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {