Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion .github/sync-repo-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ branchProtectionRules:
requiredStatusCheckContexts:
- "cla/google"
- "lint"
- "mysql-integration-test-pr (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py38 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py39 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py310 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py311 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py312 (langchain-cloud-sql-testing)"
- "conventionalcommits.org"
- "header-check"
# - Add required status checks like presubmit tests
Expand Down
81 changes: 81 additions & 0 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# DEVELOPER.md

## Versioning

This library follows [Semantic Versioning](http://semver.org/).

## Processes

### Conventional Commit messages

This repository uses tool [Release Please](https://github.com/googleapis/release-please) to create GitHub and PyPi releases. It does so by parsing your
git history, looking for [Conventional Commit messages](https://www.conventionalcommits.org/),
and creating release PRs.

Learn more by reading [How should I write my commits?](https://github.com/googleapis/release-please?tab=readme-ov-file#how-should-i-write-my-commits)

## Testing

### Run tests locally

1. Set environment variables for `INSTANCE_ID`, `DB_NAME`, `TABLE_NAME`, `REGION`, `DB_USER`, `DB_PASSWORD`

1. Run pytest to automatically run all tests:

```bash
pytest
```

### CI Platform Setup

Cloud Build is used to run tests against Google Cloud resources in test project: langchain-cloud-sql-testing.
Each test has a corresponding Cloud Build trigger, see [all triggers][triggers].
These tests are registered as required tests in `.github/sync-repo-settings.yaml`.

#### Trigger Setup

Cloud Build triggers (for Python versions 3.8 to 3.11) were created with the following specs:

```YAML
name: mysql-integration-test-pr-py38
description: Run integration tests on PR for Python 3.8
filename: integration.cloudbuild.yaml
github:
name: langchain-google-cloud-sql-mysql-python
owner: googleapis
pullRequest:
branch: .*
commentControl: COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY
ignoredFiles:
- docs/**
- .kokoro/**
- .github/**
- "*.md"
substitutions:
_INSTANCE_ID: <ADD_VALUE>
_DB_NAME: <ADD_VALUE>
_REGION: us-central1
_VERSION: "3.8"
```

Use `gcloud builds triggers import --source=trigger.yaml` create triggers via the command line

#### Project Setup

1. Create an Cloud SQL for PostgreSQL instance and database
1. Setup Cloud Build triggers (above)

#### Run tests with Cloud Build

* Run integration test:

```bash
gcloud builds submit --config integration.cloudbuild.yaml --region us-central1 --substitutions=_INSTANCE_ID=$INSTANCE_ID,_DB_NAME=$DB_NAME,_REGION=$REGION
```

#### Trigger

To run Cloud Build tests on GitHub from external contributors, ie RenovateBot, comment: `/gcbrun`.


[triggers]: https://console.cloud.google.com/cloud-build/triggers?e=13802955&project=langchain-cloud-sql-testing
30 changes: 17 additions & 13 deletions integration.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,39 @@

steps:
- id: Install dependencies
name: python:3.11
name: python:${_VERSION}
entrypoint: pip
args: ["install", "--user", "-r", "requirements.txt"]

- id: Install module (and test requirements)
name: python:3.11
name: python:${_VERSION}
entrypoint: pip
args: ["install", ".[test]", "--user"]

- id: Run integration tests
name: python:3.11
name: python:${_VERSION}
entrypoint: python
args: ["-m", "pytest"]
env:
- 'PROJECT_ID=$PROJECT_ID'
- 'INSTANCE_ID=$_INSTANCE_ID'
- 'DB_NAME=$_DB_NAME'
- 'TABLE_NAME=test-$BUILD_ID'
- 'REGION=$_REGION'
secretEnv: ['DB_USER', 'DB_PASSWORD']
- "PROJECT_ID=$PROJECT_ID"
- "INSTANCE_ID=$_INSTANCE_ID"
- "DB_NAME=$_DB_NAME"
- "TABLE_NAME=test-$BUILD_ID"
- "REGION=$_REGION"
secretEnv: ["DB_USER", "DB_PASSWORD"]

availableSecrets:
secretManager:
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-username/versions/1
env: 'DB_USER'
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-password/versions/1
env: 'DB_PASSWORD'
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-username/versions/1
env: "DB_USER"
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-password/versions/1
env: "DB_PASSWORD"

substitutions:
_INSTANCE_ID: test-instance
_REGION: us-central1
_DB_NAME: test
_VERSION: "3.8"

options:
dynamicSubstitutions: true
3 changes: 1 addition & 2 deletions src/langchain_google_cloud_sql_mysql/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from collections.abc import Iterable
from typing import Any, Dict, Iterator, List, Optional, Sequence, cast
from typing import Any, Dict, Iterable, Iterator, List, Optional, cast

import pymysql
import sqlalchemy
Expand Down
21 changes: 15 additions & 6 deletions tests/integration/test_mysql_chat_message_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import uuid
from typing import Generator

import pytest
Expand All @@ -25,17 +26,21 @@
region = os.environ["REGION"]
instance_id = os.environ["INSTANCE_ID"]
db_name = os.environ["DB_NAME"]
table_name = "message_store"
table_name = "message_store" + str(uuid.uuid4())
malformed_table = "malformed_table" + str(uuid.uuid4())


@pytest.fixture(name="memory_engine")
def setup() -> Generator:
engine = MySQLEngine.from_instance(
project_id=project_id, region=region, instance=instance_id, database=db_name
project_id=project_id,
region=region,
instance=instance_id,
database=db_name,
)

# create table with malformed schema (missing 'type')
query = """CREATE TABLE malformed_table (
query = f"""CREATE TABLE `{malformed_table}` (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id TEXT NOT NULL,
data JSON NOT NULL
Expand All @@ -47,7 +52,7 @@ def setup() -> Generator:
# use default table for MySQLChatMessageHistory
with engine.connect() as conn:
conn.execute(sqlalchemy.text(f"DROP TABLE IF EXISTS `{table_name}`"))
conn.execute(sqlalchemy.text(f"DROP TABLE IF EXISTS malformed_table"))
conn.execute(sqlalchemy.text(f"DROP TABLE IF EXISTS `{malformed_table}`"))
conn.commit()


Expand All @@ -71,7 +76,9 @@ def test_chat_message_history(memory_engine: MySQLEngine) -> None:
assert len(history.messages) == 0


def test_chat_message_history_table_does_not_exist(memory_engine: MySQLEngine) -> None:
def test_chat_message_history_table_does_not_exist(
memory_engine: MySQLEngine,
) -> None:
"""Test that MySQLChatMessageHistory fails if table does not exist."""
with pytest.raises(AttributeError) as exc_info:
MySQLChatMessageHistory(
Expand All @@ -90,5 +97,7 @@ def test_chat_message_history_table_malformed_schema(
"""Test that MySQLChatMessageHistory fails if schema is malformed."""
with pytest.raises(IndexError):
MySQLChatMessageHistory(
engine=memory_engine, session_id="test", table_name="malformed_table"
engine=memory_engine,
session_id="test",
table_name=malformed_table,
)