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
658 changes: 658 additions & 0 deletions .github/workflows/tests.yml

Large diffs are not rendered by default.

180 changes: 180 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

# Distribution / packaging
build/
couchbase_analytics/_version.py

# Sphinx
docs/_build/

# VS Code
.vscode/

# tests
tests/test_logs/
CouchbaseMock*.jar
gocaves*
.pytest_cache/
test_scripts/

# rff
.ruff_cache/
67 changes: 67 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-added-large-files
- id: check-toml
- id: check-merge-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.1
hooks:
# Run the linter.
- id: ruff-check
types_or: [ python, pyi ]
# Run the formatter.
- id: ruff-format
types_or: [ python, pyi ]
- repo: https://github.com/PyCQA/bandit
rev: 1.8.6
hooks:
- id: bandit
exclude: |
(?x)^(
acouchbase_analytics/tests/|
couchbase_analytics/tests/|
tests/|
couchbase_analytics_version.py
)
args:
[
--quiet
]
- repo: local
hooks:
- id: mypy
name: mypy
entry: "./run-mypy"
language: python
additional_dependencies:
- mypy~=1.16.1
- pytest~=8.3.5
- httpx~=0.28.1
- aiohttp~=3.11.10
types:
- python
require_serial: true
verbose: true
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.7.19
hooks:
# Compile requirements
- id: pip-compile
name: pip-compile requirements.in
args: [requirements.in, --python-version, '3.9', --universal, -o, requirements.txt]
- id: pip-compile
name: pip-compile requirements-dev.in
args: [requirements-dev.in, --python-version, '3.9', --universal, -o, requirements-dev.txt]
files: ^requirements-dev\.(in|txt)$
- id: pip-compile
name: pip-compile requirements-sphinx.in
args: [requirements-sphinx.in, --python-version, '3.9', --universal, -o, requirements-sphinx.txt]
files: ^requirements-sphinx\.(in|txt)$
Empty file added LICENSE
Empty file.
10 changes: 10 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
include *.txt LICENSE CONTRIBUTING.md pyproject.toml couchbase_analytics_version.py
include couchbase-sdk-analytics-python-black-duck-manifest.yaml
include couchbase_analytics/common/_core/nonprod_certificates/*.pem
include couchbase_analytics/common/_core/capella_certificates/*.pem
recursive-include couchbase_analytics *.py
exclude couchbase_analytics/tests/*.py
recursive-include acouchbase_analytics *.py
exclude acouchbase_analytics/tests/*.py
global-exclude *.py[cod] *.DS_Store
exclude .git .gitignore .gitmodules gocaves* *.jar .clang* .cmake* .pre* .flake* MANIFEST.in
133 changes: 132 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,133 @@
# Couchbase Python Analytics Client
Python client for [Couchbase](https://couchbase.com) Analytics.
Python client for [Couchbase](https://couchbase.com) Analytics.

Currently Python 3.9 - Python 3.13 is supported.

The Analytics SDK supports static typing. Currently only [mypy](https://github.com/python/mypy) is supported. You mileage may vary (YMMV) with the use of other static type checkers (e.g. [pyright](https://github.com/microsoft/pyright)).

# Installing the SDK<a id="installing-the-sdk"></a>

Until a version is available on PyPI, the SDK can be installed via pip with the following command (note the `dev` branch in the url).

Install the SDK via `pip`:
```console
python3 -m pip install git+https://github.com/couchbaselabs/analytics-python-client@dev
```

# Using the SDK<a id="using-the-sdk"></a>

Some more examples are provided in the [examples directory](https://github.com/couchbaselabs/analytics-python-client/tree/dev/examples).

**Connecting and executing a query**
```python
from couchbase_analytics.cluster import Cluster
from couchbase_analytics.credential import Credential
from couchbase_analytics.options import QueryOptions


def main() -> None:
# Update this to your cluster
# IMPORTANT: The appropriate port needs to be specified. The SDK's default ports are 80 (http) and 443 (https).
# If attempting to connect to Capella, the correct ports are most likely to be 8095 (http) and 18095 (https).
# Capella example: https://cb.2xg3vwszqgqcrsix.cloud.couchbase.com:18095
endpoint = 'https://--your-instance--'
username = 'username'
pw = 'password'
# User Input ends here.

cred = Credential.from_username_and_password(username, pw)
cluster = Cluster.create_instance(endpoint, cred)

# Execute a query and buffer all result rows in client memory.
statement = 'SELECT * FROM `travel-sample`.inventory.airline LIMIT 10;'
res = cluster.execute_query(statement)
all_rows = res.get_all_rows()
for row in all_rows:
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

# Execute a query and process rows as they arrive from server.
statement = 'SELECT * FROM `travel-sample`.inventory.airline WHERE country="United States" LIMIT 10;'
res = cluster.execute_query(statement)
for row in res.rows():
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

# Execute a streaming query with positional arguments.
statement = 'SELECT * FROM `travel-sample`.inventory.airline WHERE country=$1 LIMIT $2;'
res = cluster.execute_query(statement, QueryOptions(positional_parameters=['United States', 10]))
for row in res:
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

# Execute a streaming query with named arguments.
statement = 'SELECT * FROM `travel-sample`.inventory.airline WHERE country=$country LIMIT $limit;'
res = cluster.execute_query(statement, QueryOptions(named_parameters={'country': 'United States',
'limit': 10}))
for row in res.rows():
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')


if __name__ == '__main__':
main()

```

## Using the async API
```python
import asyncio

from acouchbase_analytics.cluster import AsyncCluster
from acouchbase_analytics.credential import Credential
from acouchbase_analytics.options import QueryOptions


async def main() -> None:
# Update this to your cluster
# IMPORTANT: The appropriate port needs to be specified. The SDK's default ports are 80 (http) and 443 (https).
# If attempting to connect to Capella, the correct ports are most likely to be 8095 (http) and 18095 (https).
# Capella example: https://cb.2xg3vwszqgqcrsix.cloud.couchbase.com:18095
endpoint = 'https://--your-instance--'
username = 'username'
pw = 'password'
# User Input ends here.

cred = Credential.from_username_and_password(username, pw)
cluster = AsyncCluster.create_instance(endpoint, cred)

# Execute a query and buffer all result rows in client memory.
statement = 'SELECT * FROM `travel-sample`.inventory.airline LIMIT 10;'
res = await cluster.execute_query(statement)
all_rows = await res.get_all_rows()
# NOTE: all_rows is a list, _do not_ use `async for`
for row in all_rows:
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

# Execute a query and process rows as they arrive from server.
statement = 'SELECT * FROM `travel-sample`.inventory.airline WHERE country="United States" LIMIT 10;'
res = await cluster.execute_query(statement)
async for row in res.rows():
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

# Execute a streaming query with positional arguments.
statement = 'SELECT * FROM `travel-sample`.inventory.airline WHERE country=$1 LIMIT $2;'
res = await cluster.execute_query(statement, QueryOptions(positional_parameters=['United States', 10]))
async for row in res:
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

# Execute a streaming query with named arguments.
statement = 'SELECT * FROM `travel-sample`.inventory.airline WHERE country=$country LIMIT $limit;'
res = await cluster.execute_query(statement, QueryOptions(named_parameters={'country': 'United States',
'limit': 10}))
async for row in res.rows():
print(f'Found row: {row}')
print(f'metadata={res.metadata()}')

if __name__ == '__main__':
asyncio.run(main())

```
Loading