Skip to content

Commit fa88b33

Browse files
committed
First commit, finish v1.0.0
1 parent ae4086c commit fa88b33

19 files changed

+402
-4
lines changed

.gitignore

-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ __pycache__/
33
*.py[cod]
44
*$py.class
55

6-
# C extensions
7-
*.so
8-
96
# Distribution / packaging
107
.Python
118
build/

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "groupcache"]
2+
path = groupcache
3+
url = https://github.com/golang/groupcache.git

MANIFEST.in

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include LICENSE.txt
2+
recursive-include tests *.py

Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
.PHONY: help
3+
help: ### Display this help screen.
4+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
5+
6+
.PHONY: deps
7+
deps: ### Package the runtime requirements.
8+
@pip freeze > requirements.txt
9+
10+
.PHONY: lint
11+
lint: ### Lint the source code.
12+
@pyflakes pygroupcache/*.py
13+
@pycodestyle pygroupcache/*.py --ignore=W293,E121,E125,E402,E501

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
# groupcache-py
1+
# groupcache-py
2+
3+
This is a Python binding for groupcache.
4+
5+
Groupcache is a distributed caching and cache-filling library, intended as a replacement for a pool of memcached nodes in many cases.

groupcache

Submodule groupcache added at 41bb18b

groupcache-wrapper/Makefile

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
GROUPCACHE_GIT_HASH := $(shell cd ../groupcache && git rev-parse --short HEAD)
3+
4+
.PHONY: help
5+
help: ### Display this help screen.
6+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
7+
8+
ifeq ($(race), 1)
9+
BUILD_FLAGS := -race
10+
endif
11+
12+
ifeq ($(gc_debug), 1)
13+
BUILD_FLAGS += -gcflags=all="-N -l"
14+
endif
15+
16+
.PHONY: build
17+
build: ## Build groupcache dynamic library.
18+
@(GOOS=linux GOARCH=amd64 go build -buildmode=c-shared -o ../libs/groupcache_$(GROUPCACHE_GIT_HASH).so main.go)
19+
@(ln -sf ../libs/groupcache_$(GROUPCACHE_GIT_HASH).h ../libs/groupcache.h)
20+
@(ln -sf ../libs/groupcache_$(GROUPCACHE_GIT_HASH).so ../libs/groupcache.so)
21+
22+
.PHONY: clean
23+
clean: ## Clean all objects.
24+
@(rm -f ../libs/groupcache_$(GROUPCACHE_GIT_HASH).h)
25+
@(rm -f ../libs/groupcache_$(GROUPCACHE_GIT_HASH).so)

groupcache-wrapper/go.mod

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module github.com/amazingchow/groupcache-py
2+
3+
go 1.18
4+
5+
require github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
6+
7+
require (
8+
github.com/golang/protobuf v1.5.3 // indirect
9+
google.golang.org/protobuf v1.26.0 // indirect
10+
)

groupcache-wrapper/go.sum

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
2+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
3+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
4+
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
5+
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
6+
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
7+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
9+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
10+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
11+
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
12+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

groupcache-wrapper/main.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"log"
7+
"net/http"
8+
9+
"github.com/golang/groupcache"
10+
)
11+
12+
import "C"
13+
14+
// temporary storage to be able to set data
15+
var Store = map[string]string{}
16+
17+
var peers *groupcache.HTTPPool = nil
18+
var cache *groupcache.Group = nil
19+
var srv *http.Server = nil
20+
21+
//export cache_set
22+
func cache_set(key *C.char, value *C.char) *C.char {
23+
// set the value in the cache.
24+
25+
// groupcache does not have a way to set a value in the cache (pass through cache),
26+
// so we fake it by setting value in global store and then getting the value immediately.
27+
// Ideally, this is switched out with a backend that retrieves the value you want to cache.
28+
var gkey = C.GoString(key)
29+
var gvalue = C.GoString(value)
30+
Store[gkey] = gvalue
31+
var data string
32+
cache.Get(context.TODO(), gkey, groupcache.StringSink(&data))
33+
delete(Store, gkey)
34+
return C.CString(data)
35+
}
36+
37+
//export cache_get
38+
func cache_get(key *C.char) *C.char {
39+
// get the value from the cache.
40+
var data string
41+
cache.Get(context.TODO(), C.GoString(key), groupcache.StringSink(&data))
42+
return C.CString(data)
43+
}
44+
45+
//export setup
46+
func setup(addr *C.char, baseUrl *C.char) {
47+
// setup the cache server.
48+
done := make(chan bool)
49+
go func() {
50+
log.Printf("Setting up cache server node at %s...\n", C.GoString(addr))
51+
peers = groupcache.NewHTTPPool(C.GoString(baseUrl))
52+
cache = groupcache.NewGroup("Cache", 64<<20, groupcache.GetterFunc(
53+
func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
54+
v, ok := Store[key]
55+
if !ok {
56+
return errors.New("cache key not found")
57+
}
58+
dest.SetBytes([]byte(v))
59+
return nil
60+
}))
61+
router := http.NewServeMux()
62+
router.Handle("/", http.HandlerFunc(peers.ServeHTTP))
63+
srv := &http.Server{Addr: C.GoString(addr), Handler: router}
64+
close(done)
65+
if err := srv.ListenAndServe(); err != nil {
66+
log.Fatal("ListenAndServe: ", err)
67+
}
68+
}()
69+
<-done
70+
// do it in a go routine so we don't block
71+
log.Printf("Running cache server node at %s.\n", C.GoString(addr))
72+
}
73+
74+
//export initialized
75+
func initialized() *C.char {
76+
// check if cache is initialized. If it is, then we can use it.
77+
var flag string
78+
if cache != nil {
79+
flag = "1"
80+
} else {
81+
flag = "0"
82+
}
83+
return C.CString(flag)
84+
}
85+
86+
func main() {}

libs/groupcache.h

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../libs/groupcache_41bb18b.h

libs/groupcache.so

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../libs/groupcache_41bb18b.so

libs/groupcache_41bb18b.h

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* Code generated by cmd/cgo; DO NOT EDIT. */
2+
3+
/* package command-line-arguments */
4+
5+
6+
#line 1 "cgo-builtin-export-prolog"
7+
8+
#include <stddef.h> /* for ptrdiff_t below */
9+
10+
#ifndef GO_CGO_EXPORT_PROLOGUE_H
11+
#define GO_CGO_EXPORT_PROLOGUE_H
12+
13+
#ifndef GO_CGO_GOSTRING_TYPEDEF
14+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
15+
#endif
16+
17+
#endif
18+
19+
/* Start of preamble from import "C" comments. */
20+
21+
22+
23+
24+
/* End of preamble from import "C" comments. */
25+
26+
27+
/* Start of boilerplate cgo prologue. */
28+
#line 1 "cgo-gcc-export-header-prolog"
29+
30+
#ifndef GO_CGO_PROLOGUE_H
31+
#define GO_CGO_PROLOGUE_H
32+
33+
typedef signed char GoInt8;
34+
typedef unsigned char GoUint8;
35+
typedef short GoInt16;
36+
typedef unsigned short GoUint16;
37+
typedef int GoInt32;
38+
typedef unsigned int GoUint32;
39+
typedef long long GoInt64;
40+
typedef unsigned long long GoUint64;
41+
typedef GoInt64 GoInt;
42+
typedef GoUint64 GoUint;
43+
typedef __SIZE_TYPE__ GoUintptr;
44+
typedef float GoFloat32;
45+
typedef double GoFloat64;
46+
typedef float _Complex GoComplex64;
47+
typedef double _Complex GoComplex128;
48+
49+
/*
50+
static assertion to make sure the file is being used on architecture
51+
at least with matching size of GoInt.
52+
*/
53+
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
54+
55+
#ifndef GO_CGO_GOSTRING_TYPEDEF
56+
typedef _GoString_ GoString;
57+
#endif
58+
typedef void *GoMap;
59+
typedef void *GoChan;
60+
typedef struct { void *t; void *v; } GoInterface;
61+
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
62+
63+
#endif
64+
65+
/* End of boilerplate cgo prologue. */
66+
67+
#ifdef __cplusplus
68+
extern "C" {
69+
#endif
70+
71+
extern char* cache_set(char* key, char* value);
72+
extern char* cache_get(char* key);
73+
extern void setup(char* addr, char* baseUrl);
74+
extern char* initialized();
75+
76+
#ifdef __cplusplus
77+
}
78+
#endif

libs/groupcache_41bb18b.so

11 MB
Binary file not shown.

pygroupcache/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# -*- coding: utf-8 -*-
2+
from .groupcache import get, set, setup, initialized
3+
4+
__version__ = "1.0.0"
5+
__all__ = [
6+
"get",
7+
"set",
8+
"setup",
9+
"initialized",
10+
]

pygroupcache/groupcache.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# -*- coding: utf-8 -*-
2+
import ctypes
3+
import os
4+
from typing import Union
5+
6+
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7+
lib = ctypes.cdll.LoadLibrary(os.path.join(root_path, "libs/groupcache.so"))
8+
9+
gget = lib.cache_get
10+
gget.argtypes = [ctypes.c_char_p]
11+
gget.restype = ctypes.c_char_p
12+
gset = lib.cache_set
13+
gset.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
14+
gset.restype = ctypes.c_char_p
15+
gsetup = lib.setup
16+
gsetup.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
17+
ginitialized = lib.initialized
18+
ginitialized.restype = ctypes.c_char_p
19+
20+
21+
def get(key: str) -> str:
22+
"""
23+
Retrieves the value associated with the given key from the cache.
24+
25+
Args:
26+
key (str): The key to retrieve the value for.
27+
28+
Returns:
29+
str: The value associated with the key, or an empty string if the key is not found.
30+
"""
31+
r = gget(ctypes.c_char_p(key.encode("utf8")))
32+
return r.decode("utf8") if isinstance(r, bytes) else ""
33+
34+
35+
def set(key: str, value: Union[str, bytes]) -> str:
36+
"""
37+
Sets the value for the given key in the cache.
38+
39+
Args:
40+
key (str): The key to set the value for.
41+
value (Union[str, bytes]): The value to set for the key. If it's a string, it will be encoded as UTF-8.
42+
43+
Returns:
44+
str: The previous value associated with the key, or an empty string if the key is not found.
45+
"""
46+
if not isinstance(value, bytes):
47+
value = value.encode("utf-8")
48+
r = gset(ctypes.c_char_p(key.encode("utf8")), ctypes.c_char_p(value))
49+
return r.decode("utf8") if isinstance(r, bytes) else ""
50+
51+
52+
def setup(addr: str, base_url: str):
53+
"""
54+
Sets up the cache with the given address.
55+
56+
Args:
57+
addr (str): The address of the cache.
58+
59+
Returns:
60+
None
61+
"""
62+
gsetup(ctypes.c_char_p(addr.encode("utf8")), ctypes.c_char_p(base_url.encode("utf8")))
63+
64+
65+
def initialized() -> bool:
66+
"""
67+
Checks if the cache is initialized.
68+
69+
Returns:
70+
bool: True if the cache is initialized, False otherwise.
71+
"""
72+
return ginitialized().decode("utf8") == "1"

pygroupcache/test_groupcache.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: utf-8 -*-
2+
import os
3+
import sys
4+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5+
6+
import logging
7+
import unittest
8+
9+
from pygroupcache import get, set, setup, initialized
10+
11+
12+
class PyGroupCacheTest(unittest.TestCase):
13+
logger = logging.getLogger(__name__)
14+
15+
def setUp(self):
16+
setup("localhost:15555", "http://localhost:15555")
17+
if initialized():
18+
self.initialized = True
19+
else:
20+
self.initialized = False
21+
22+
def test_set_and_get_existing_or_non_existing_key(self):
23+
if not self.initialized:
24+
self.skipTest("Cache not initialized")
25+
key = "existing_key"
26+
value = "existing_value"
27+
set(key, value)
28+
ret_value = get(key)
29+
self.assertEqual(ret_value, value)
30+
key = "non_existing_key"
31+
ret_value = get(key)
32+
self.assertEqual(ret_value, "")
33+
34+
def tearDown(self):
35+
if self.initialized:
36+
pass
37+
38+
39+
if __name__ == "__main__":
40+
unittest.main()

setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[bdist_wheel]
2+
universal = 1

0 commit comments

Comments
 (0)