Skip to content

Commit

Permalink
add index.d.ts (#56)
Browse files Browse the repository at this point in the history
* add `index.d.ts`

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* use string key onlies

* remove keys not being strings test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
PythonFZ and pre-commit-ci[bot] authored Nov 4, 2024
1 parent 31d6ca5 commit 44007c0
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 98 deletions.
29 changes: 10 additions & 19 deletions js/dict.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { Client as ZnSocketClient } from "./client.js";

function toJSONStringified(value) {
return JSON.stringify(value);
}

function fromJSONStringified(value) {
return JSON.parse(value);
}

export class Dict {
constructor({ client, socket, key, callbacks }) {
Expand Down Expand Up @@ -49,10 +42,10 @@ export class Dict {
if (this._socket) {
this._socket.emit("refresh", {
target: this._key,
data: { keys: [toJSONStringified(key)] }, // Ensure key is stringified
data: { keys: [key] },
});
}
return this._client.hSet(this._key, toJSONStringified(key), toJSONStringified(value)); // Stringify both key and value
return this._client.hSet(this._key, key, JSON.stringify(value));
}

async update(dict) {
Expand All @@ -62,23 +55,23 @@ export class Dict {
if (this._socket) {
this._socket.emit("refresh", {
target: this._key,
data: { keys: Object.keys(dict).map(key => toJSONStringified(key)) }, // Stringify keys
data: { keys: Object.keys(dict) },
});
}

const entries = Object.entries(dict).map(([key, value]) => [
toJSONStringified(key), // Stringify the key
toJSONStringified(value), // Stringify the value
key,
JSON.stringify(value),
]);
return this._client.hMSet(this._key, Object.fromEntries(entries));
}

async get(key) {
return this._client.hGet(this._key, toJSONStringified(key)).then((value) => {
return this._client.hGet(this._key, key).then((value) => {
if (value === null) {
return null;
}
return fromJSONStringified(value); // Parse the value
return JSON.parse(value); // Parse the value
});
}

Expand All @@ -87,27 +80,25 @@ export class Dict {
}

async keys() {
const keys = await this._client.hKeys(this._key);
return keys.map((x) => fromJSONStringified(x)); // Parse the keys
return this._client.hKeys(this._key);
}

async values() {
const values = await this._client.hVals(this._key);
return values.map((x) => fromJSONStringified(x)); // Parse the values
return values.map((x) => JSON.parse(x)); // Parse the values
}

async entries() { // Renamed from items to entries
const entries = await this._client.hGetAll(this._key);
return Object.entries(entries).map(
([key, value]) => [fromJSONStringified(key), fromJSONStringified(value)] // Parse both keys and values
([key, value]) => [key, JSON.parse(value)]
);
}

onRefresh(callback) {
if (this._socket) {
this._refreshCallback = async ({ target, data }) => {
if (target === this._key) {
data.keys = data.keys.map((key) => fromJSONStringified(key));
callback(data);
}
};
Expand Down
95 changes: 95 additions & 0 deletions js/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Socket } from "socket.io-client";
import { ZnSocketClient } from "./client"; // Assuming client.d.ts is already defined

interface ClientOptions {
url?: string;
namespace?: string;
socket?: Socket;
}

export class Client {
constructor(options: ClientOptions);
connect(): Promise<string>;
on<T extends string>(event: T, callback: (data: any) => void): void;
off<T extends string>(event: T, callback?: (data: any) => void): void;
emit<T extends string>(event: T, data: any): Promise<any>;
disconnect(): void;

lLen(key: string): Promise<number>;
lIndex(key: string, index: number): Promise<string | null>;
lSet(key: string, index: number, value: string): Promise<any>;
lRem(key: string, count: number, value: string): Promise<any>;
lRange(key: string, start: number, end: number): Promise<string[]>;
rPush(key: string, value: string): Promise<any>;
lPush(key: string, value: string): Promise<any>;
hGet(key: string, field: string): Promise<string | null>;
hSet(key: string, field: string, value: string): Promise<any>;
hMSet(key: string, mapping: Record<string, string>): Promise<any>;
hDel(key: string, field: string): Promise<any>;
del(key: string): Promise<any>;
hExists(key: string, field: string): Promise<boolean>;
hLen(key: string): Promise<number>;
hKeys(key: string): Promise<string[]>;
hVals(key: string): Promise<string[]>;
hGetAll(key: string): Promise<Record<string, string>>;
flushAll(): Promise<any>;
}

export const createClient: (options: ClientOptions) => Client;


interface ListCallbacks {
push?: (value: any) => Promise<any>;
set?: (value: any) => Promise<any>;
clear?: () => Promise<any>;
}

export class List {
private readonly _client: ZnSocketClient;
private readonly _key: string;
private readonly _callbacks?: ListCallbacks;
private readonly _socket?: ZnSocketClient; // Can be null if not provided
private readonly _refreshCallback?: (data: { target: string; data: any }) => void;

constructor(options: { client: ZnSocketClient; key: string; socket?: ZnSocketClient; callbacks?: ListCallbacks });

length(): Promise<number>;
slice(start: number, end: number): Promise<any[]>;
push(value: any): Promise<any>;
set(index: number, value: any): Promise<any>;
clear(): Promise<any>;
get(index: number): Promise<any | null>;

onRefresh(callback: (data: { start?: number; indices?: number[] }) => void): void;
offRefresh(): void;

[Symbol.asyncIterator](): AsyncIterator<any | undefined>;
}


interface DictCallbacks {
set?: (value: any) => Promise<any>;
update?: (value: Record<string, any>) => Promise<any>;
}

export class Dict {
private readonly _client: ZnSocketClient;
private readonly _socket?: ZnSocketClient; // Can be null if not provided
private readonly _key: string;
private readonly _callbacks?: DictCallbacks;
private readonly _refreshCallback?: (data: { target: string; data: any }) => void;

constructor(options: { client: ZnSocketClient; socket?: ZnSocketClient; key: string; callbacks?: DictCallbacks });

length(): Promise<number>;
set(key: string, value: any): Promise<any>;
update(dict: Record<string, any>): Promise<any>;
get(key: string): Promise<any | null>;
clear(): Promise<any>;
keys(): Promise<string[]>;
values(): Promise<any[]>;
entries(): Promise<[string, any][]>; // Renamed from items to entries

onRefresh(callback: (data: { keys?: string[]; indices?: number[] }) => void): void;
offRefresh(): void;
}
14 changes: 0 additions & 14 deletions js_tests/native.dict.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,3 @@ test("native_dict_json_test", async () => {
// expect(await dct.entries()).toEqual([[5, 5], [6, "6"], [7, 7], [8, "8"]]);
// expect(await dct.entries()).toEqual([["5", 5], ["6", "6"], ["7", 7], ["8", "8"]]); // WRONG! but what we get right now
});

// !! keys are for some reason always strings

// not really a test but a reminder of what JSON does
test("native_dict_toJSONStringified", async () => {
expect(JSON.stringify(5)).toBe("5");
expect(JSON.stringify("5")).toBe("\"5\"");
expect(JSON.stringify({ a: 5 })).toBe("{\"a\":5}");

expect(JSON.parse("5")).toBe(5);
expect(JSON.parse("\"5\"")).toBe("5");
expect(JSON.parse("{\"a\":5}")).toEqual({ a: 5 });
// expect(5).toBe("5");
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.2.4",
"description": "JS interface for the python znsocket package",
"main": "js/index.js",
"types": "js/index.d.ts",
"type": "module",
"directories": {
"test": "tests"
Expand Down
2 changes: 1 addition & 1 deletion tests/js_testing/test_node_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_dict_set_znsocket(znsclient, run_npm_test, request):
run_npm_test(request.node.name, client_url=znsclient.address)

assert mock.call_count == 2
assert mock.call_args_list == [call({"keys": ['"b"']}), call({"keys": ['"a"']})]
assert mock.call_args_list == [call({"keys": ["b"]}), call({"keys": ["a"]})]

assert dct["a"] == {"lorem": "ipsum"}
assert dct["b"] == "25"
71 changes: 22 additions & 49 deletions tests/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,55 +152,28 @@ def test_dict_equal(a, b, request):
assert dct1 != "unsupported"


@pytest.mark.parametrize(
"client", ["znsclient", "znsclient_w_redis", "redisclient", "empty"]
)
def test_dct_similar_keys(client, request):
c = request.getfixturevalue(client)
if c is not None:
dct = znsocket.Dict(r=c, key="list:test", repr_type="full")
else:
dct = {}

dct.update({1: 1, "1": "1", None: "None"})
assert dct[1] == 1
assert dct["1"] == "1"
assert dct[None] == "None"

assert dct == {1: 1, "1": "1", None: "None"}
if c is not None:
assert repr(dct) == "Dict({1: 1, '1': '1', None: 'None'})"

del dct[1]
assert dct == {"1": "1", None: "None"}
del dct["1"]
assert dct == {None: "None"}
del dct[None]
assert dct == {}


@pytest.mark.parametrize(
"client", ["znsclient", "znsclient_w_redis", "redisclient", "empty"]
)
def test_dct_None_key_values(client, request):
c = request.getfixturevalue(client)
if c is not None:
dct = znsocket.Dict(r=c, key="list:test", repr_type="full")
else:
dct = {}

dct[None] = "None"
dct["None"] = None
assert dct[None] == "None"
assert dct["None"] is None
assert dct == {None: "None", "None": None}
if c is not None:
assert repr(dct) == "Dict({None: 'None', 'None': None})"

assert list(dct) == [None, "None"]
assert list(dct.keys()) == [None, "None"]
assert list(dct.values()) == ["None", None]
assert list(dct.items()) == [(None, "None"), ("None", None)]
# @pytest.mark.parametrize(
# "client", ["znsclient", "znsclient_w_redis", "redisclient", "empty"]
# )
# def test_dct_similar_keys(client, request):
# c = request.getfixturevalue(client)
# if c is not None:
# dct = znsocket.Dict(r=c, key="list:test", repr_type="full")
# else:
# dct = {}

# dct.update({1: 1, "1": "1"})
# assert dct[1] == 1
# assert dct["1"] == "1"
# assert dct == {1: 1, "1": "1"}
# if c is not None:
# assert repr(dct) == "Dict({1: 1, '1': '1'})"

# del dct[1]
# assert dct == {"1": "1"}
# del dct["1"]
# assert dct == {}
# REDIS can not differentiate between int/float and str keys


@pytest.mark.parametrize(
Expand Down
2 changes: 1 addition & 1 deletion znsocket/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class RefreshTypeDict(t.TypedDict):
stop: NotRequired[int | None]
step: NotRequired[int | None]
indices: NotRequired[list[int]]
keys: NotRequired[list[str]]
keys: NotRequired[list[str | int | float]]


class RefreshDataTypeDict(t.TypedDict):
Expand Down
26 changes: 12 additions & 14 deletions znsocket/objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ def __init__(
if callbacks:
self._callbacks.update(callbacks)

def __getitem__(self, key: str | float | int) -> t.Any:
value = self.redis.hget(self.key, json.dumps(key))
def __getitem__(self, key: str) -> t.Any:
value = self.redis.hget(self.key, key)
if value is None:
raise KeyError(key) # TODO: items can not be None?
value = _decode(self, value)
Expand All @@ -317,14 +317,14 @@ def __getitem__(self, key: str | float | int) -> t.Any:
value = Dict(r=self.redis, key=key)
return value

def __setitem__(self, key: str | float | int, value: t.Any) -> None:
def __setitem__(self, key: str, value: t.Any) -> None:
if isinstance(value, List):
value = f"znsocket.List:{value.key}"
if isinstance(value, Dict):
if value.key == self.key:
raise ValueError("Can not set circular reference to self")
value = f"znsocket.Dict:{value.key}"
self.redis.hset(self.key, _encode(self, key), _encode(self, value))
self.redis.hset(self.key, key, _encode(self, value))
if callback := self._callbacks["setitem"]:
callback(key, value)
if self.socket is not None:
Expand All @@ -333,11 +333,9 @@ def __setitem__(self, key: str | float | int, value: t.Any) -> None:
self.socket.sio.emit(f"refresh", refresh_data, namespace="/znsocket")

def __delitem__(self, key: str) -> None:
json_key = _encode(self, key)

if not self.redis.hexists(self.key, json_key):
if not self.redis.hexists(self.key, key):
raise KeyError(key)
self.redis.hdel(self.key, json_key)
self.redis.hdel(self.key, key)
if callback := self._callbacks["delitem"]:
callback(key)
if self.socket is not None:
Expand All @@ -351,23 +349,23 @@ def __iter__(self):
def __len__(self) -> int:
return self.redis.hlen(self.key)

def keys(self) -> list[t.Any]:
return [_decode(self, k) for k in self.redis.hkeys(self.key)]
def keys(self) -> list[str]:
return self.redis.hkeys(self.key)

def values(self) -> list[t.Any]:
response = []
for v in self.redis.hvals(self.key):
response.append(_decode(self, v))
return response

def items(self) -> list[t.Tuple[t.Any, t.Any]]:
def items(self) -> list[t.Tuple[str, t.Any]]:
response = []
for k, v in self.redis.hgetall(self.key).items():
response.append((_decode(self, k), _decode(self, v)))
response.append((k, _decode(self, v)))
return response

def __contains__(self, key: object) -> bool:
return self.redis.hexists(self.key, _encode(self, key))
def __contains__(self, key: str) -> bool:
return self.redis.hexists(self.key, key)

def __repr__(self) -> str:
if self.repr_type == "keys":
Expand Down

0 comments on commit 44007c0

Please sign in to comment.