Skip to content

Commit

Permalink
Merge pull request #17 from sitzz/15-add-tests
Browse files Browse the repository at this point in the history
15 add tests
  • Loading branch information
sitzz authored Dec 2, 2024
2 parents dbd902c + 1b9d3c3 commit ca0d26a
Show file tree
Hide file tree
Showing 23 changed files with 846 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
.idea
build/*
*.egg-info

__pycache__
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion couchbase_helper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

__all__ = ["CouchbaseHelper", "Session", "Timeout", "BucketNotSet", "ScopeNotSet"]
__author__ = "Thomas 'sitzz' Vang <[email protected]>"
__version__ = "0.0.9b"
__version__ = "0.0.10b"
13 changes: 13 additions & 0 deletions couchbase_helper/fake/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from couchbase_helper.fake.bucket import Bucket
from couchbase_helper.fake.cluster import Cluster
from couchbase_helper.fake.collection import Collection
from couchbase_helper.fake.scope import Scope
from couchbase_helper.fake.session import Session

__all__ = [
"Bucket",
"Cluster",
"Collection",
"Scope",
"Session",
]
11 changes: 11 additions & 0 deletions couchbase_helper/fake/_datetime_hack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import datetime
import sys


def utcnow():
if sys.version_info >= (3, 12):
from datetime import UTC

return datetime.datetime.now(UTC)
else:
return datetime.datetime.utcnow()
43 changes: 43 additions & 0 deletions couchbase_helper/fake/bucket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from couchbase.logic.bucket import BucketLogic

from couchbase_helper.fake.collection import Collection
from couchbase_helper.fake.scope import Scope


class Bucket(BucketLogic):
def __init__(self, cluster, bucket_name):
super().__init__(cluster, bucket_name)
self._connected = False

def close(self):
if self.connected:
super()._open_or_close_bucket(open_bucket=False)
self._destroy_connection()

def default_scope(self):
return self.scope(Scope.default_name())

def scope(self, name):
return Scope(self, name)

def collection(self, collection_name):
scope = self.default_scope()
return scope.collection(collection_name)

def default_collection(self):
scope = self.default_scope()
return scope.collection(Collection.default_name())

def ping(self):
pass

def view_query(self, design_doc, view_name, *view_options, **kwargs):
pass

def collections(self):
# return CollectionManager(self.connection, self.name)
pass

def view_indexes(self):
# return ViewIndexManager(self.connection, self.name)
pass
78 changes: 78 additions & 0 deletions couchbase_helper/fake/cluster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from datetime import timedelta
from time import sleep, time
from random import randint

from couchbase.exceptions import UnAmbiguousTimeoutException
from couchbase.logic.cluster import ClusterLogic
from couchbase.serializer import DefaultJsonSerializer
from couchbase.transcoder import JSONTranscoder

from couchbase_helper.fake._datetime_hack import utcnow
from couchbase_helper.fake.bucket import Bucket


class Cluster(ClusterLogic):
def __init__(self, connstr, *options, **kwargs):
super().__init__(connstr, *options, **kwargs)
self._connected = False
self._default_serializer = DefaultJsonSerializer()
self._transcoder = JSONTranscoder()

def close(self):
self._connected = False

def bucket(self, bucket_name):
return Bucket(self, bucket_name)

def cluster_info(self):
pass

def ping(self, *opts, **kwargs):
pass

def diagnostics(self, *opts, **kwargs):
pass

@staticmethod
def wait_until_ready(timeout: timedelta, *opts, **kwargs):
cutoff = (utcnow() + timeout).timestamp()
wait = 1 / randint(100, 500)
if time() + wait < cutoff:
sleep(wait)
return

sleep(timeout.seconds)
raise UnAmbiguousTimeoutException

def query(self, statement, *options, **kwargs):
pass

def analytics_query(self, statement, *options, **kwargs):
pass

def search_query(self, index, query, *options, **kwargs):
pass

def search(self, index, request, *options, **kwargs):
pass

def buckets(self):
pass

def users(self):
pass

def query_indexes(self):
pass

def analytics_indexes(self):
pass

def search_indexes(self):
pass

def eventing_functions(self):
pass

def connect(self, connstr, *options, **kwargs):
self._connected = True
228 changes: 228 additions & 0 deletions couchbase_helper/fake/collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
from datetime import timedelta
from time import time_ns

from couchbase.collection import GetOptions, TouchOptions
from couchbase.logic.collection import CollectionLogic
from couchbase.pycbc_core import result
from couchbase.result import (
ExistsResult,
GetResult,
MultiGetResult,
MultiMutationResult,
MutationResult,
)

from couchbase_helper.fake._datetime_hack import utcnow
from couchbase_helper.fake.store import Store


class Collection(CollectionLogic):
def __init__(self, scope, name):
super().__init__(scope, name)
self._scope = scope
self._name = name
self._collection_name = f"{scope.name}-{name}"
self._store = Store()

def get(self, key, *opts, **kwargs):
res = result()
res.raw_result = self._store.get(self._collection_name, key)
return GetResult(res)

def get_multi(self, keys, *opts, **kwargs):
return_exceptions = self._get_kwarg("return_exceptions", kwargs, True)
res = result()
docs = {}
for key in keys:
tmp_res = result()
tmp_res.raw_result = self._store.get(self._collection_name, key)
docs[key] = tmp_res
del tmp_res

res.raw_result = docs
return MultiGetResult(res, return_exceptions=return_exceptions)

def exists(self, key, *opts, **kwargs):
res = result()
res.raw_result = self._store.get(self._collection_name, key)
return ExistsResult(res)

def insert(self, key, value, *opts, **kwargs):
expiry = self._get_expiry(**kwargs)
res = result()
try:
self._store.insert(self._collection_name, key, value, expiry)
res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
raise _exc

return MutationResult(res)

def insert_multi(self, keys_and_docs, *opts, **kwargs):
expiry = self._get_expiry(**kwargs)
return_exceptions = self._get_kwarg("return_exceptions", kwargs, True)
per_key_options = kwargs.get("per_key_options", {})
all_ok = True
ops_res = {}
for key, doc in keys_and_docs.items():
key_res = result()
if key in per_key_options:
doc_expiry = self._get_expiry(**per_key_options[key])
else:
doc_expiry = expiry

try:
self._store.insert(self._collection_name, key, doc, doc_expiry)
key_res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
all_ok = False
key_res.raw_result = _exc

ops_res[key] = key_res

ops_res["all_okay"] = all_ok
res = result()
res.raw_result = ops_res
return MultiMutationResult(res, return_exceptions)

def upsert(self, key, value, *opts, **kwargs):
expiry = self._get_expiry(**kwargs)
res = result()
try:
self._store.upsert(self._collection_name, key, value, expiry)
res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
raise _exc

return MutationResult(res)

def upsert_multi(self, keys_and_docs, *opts, **kwargs):
expiry = self._get_expiry(**kwargs)
return_exceptions = self._get_kwarg("return_exceptions", kwargs, True)
per_key_options = kwargs.get("per_key_options", {})
all_ok = True
ops_res = {}
for key, doc in keys_and_docs.items():
key_res = result()
if key in per_key_options:
doc_expiry = self._get_expiry(**per_key_options[key])
else:
doc_expiry = expiry

try:
self._store.upsert(self._collection_name, key, doc, doc_expiry)
key_res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
all_ok = False
key_res.raw_result = _exc

ops_res[key] = key_res

ops_res["all_okay"] = all_ok
res = result()
res.raw_result = ops_res
return MultiMutationResult(res, return_exceptions)

def replace(self, key, value, *opts, **kwargs):
expiry = self._get_expiry(**kwargs)
res = result()
try:
self._store.replace(self._collection_name, key, value, expiry)
res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
raise _exc

return MutationResult(res)

def remove(self, key, *opts, **kwargs):
res = result()
try:
self._store.remove(self._collection_name, key)
except Exception as _exc:
raise _exc

res.raw_result = {"cas": time_ns(), "key": key}
return MutationResult(res)

def remove_multi(self, keys, *opts, **kwargs):
return_exceptions = self._get_kwarg("return_exceptions", kwargs, True)
all_ok = True
ops_res = {}
for key in keys:
key_res = result()
try:
key_kwargs = kwargs.get("per_key_options", {}).get(key, kwargs)
self.remove(key, *opts, **key_kwargs)
key_res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
all_ok = False
key_res.raw_result = _exc

ops_res[key] = key_res

ops_res["all_okay"] = all_ok
res = result()
res.raw_result = ops_res
return MultiMutationResult(res, return_exceptions)

def touch(self, key, *opts, **kwargs):
expiry = self._get_expiry(**kwargs)
res = result()
try:
self._store.touch(self._collection_name, key, expiry)
res.raw_result = {"cas": time_ns(), "key": key}
except Exception as _exc:
raise _exc

return MutationResult(res)

def get_and_touch(self, key, **kwargs):
try:
self.touch(key, TouchOptions(), **kwargs)
return self.get(key, GetOptions(), **kwargs)
except Exception as _exc:
raise _exc

def get_and_lock(self, key, **kwargs):
lock_time = self._get_kwarg("lock_time", kwargs)
try:
res = self.get(key, GetOptions(), **kwargs)
self._store.lock(self._collection_name, key, lock_time)
return res
except Exception as _exc:
raise _exc

def unlock(self, key, cas, *opts, **kwargs):
self._store.unlock(self._collection_name, key)

def lookup_in(self, key, spec, *opts, **kwargs):
document = self.get(key, *opts, **kwargs)
value = document.value
ret = {}
for s in spec:
if s in value:
ret[s] = value[s]

return ret

@staticmethod
def default_name():
return "_default"

def _get_expiry(self, **kwargs) -> float:
expiry_delta = self._get_kwarg("expiry", kwargs)
if expiry_delta is not None:
if isinstance(expiry_delta, int):
expiry_delta = timedelta(seconds=expiry_delta)
dt_exp = utcnow() + expiry_delta
return dt_exp.timestamp()

return 0

@staticmethod
def _get_kwarg(arg, kwargs, default=None):
return kwargs.get(arg, default)

@property
def store(self):
return self._store
Empty file added couchbase_helper/fake/result.py
Empty file.
Loading

0 comments on commit ca0d26a

Please sign in to comment.