Skip to content

Commit

Permalink
Merge branch 'master' of github.com:fscdev/vkwave
Browse files Browse the repository at this point in the history
  • Loading branch information
prostomarkeloff committed Apr 8, 2020
2 parents 4583b0d + 65567fc commit 185be45
Show file tree
Hide file tree
Showing 16 changed files with 681 additions and 0 deletions.
56 changes: 56 additions & 0 deletions examples/bots/vkscript_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from vkwave.api.methods import APIOptionsRequestContext
from vkwave.bots.easy import SimpleLongPollBot, TaskManager
from vkwave.vkscript import execute
from vkwave.types.responses import ExecuteResponse

bot = SimpleLongPollBot(
tokens=["123"],
group_id=456,
)


@execute
def get_subs_names(api: APIOptionsRequestContext, group_id: int):
subs_names = []

subscribers = api.groups.getMembers(group_id=group_id)
subscribers = subscribers.items

for sub_id in subscribers:
subscriber_data = api.users.get(user_ids=sub_id)
subs_names.append(subscriber_data[0].first_name)

return subs_names


@bot.message_handler(bot.text_filter("follow"))
async def simple(event: bot.BaseEvent):
"""
Get name of each subscriber
"""

check_group = 191949777

print(get_subs_names.build(api=event.api_ctx, group_id=check_group))
"""
var subs_names=[];
var subscribers=API.groups.getMembers({group_id:1});
var subscribers=subscribers.items;
while(subscribers.length > 0){
var sub_id=subscribers.pop();
var subscriber_data=API.users.get({user_ids:sub_id});
subs_names.push(subscriber_data[0].first_name);
};
return subs_names;
"""

execute_data: ExecuteResponse = await get_subs_names(api=event.api_ctx, group_id=check_group)
print(execute_data)
await event.api_ctx.messages.send(
peer_id=event.object.object.message.peer_id, message=execute_data.response, random_id=0,
)


tm = TaskManager()
tm.add_task(bot.run)
tm.run()
Empty file added tests/vkscript/__init__.py
Empty file.
160 changes: 160 additions & 0 deletions tests/vkscript/test_execute_vkscript.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import string

import pytest

from vkwave.vkscript import execute

TEST_STRING = string.printable


@execute
def demo_build():
pass


def test_build():
code = demo_build.build()
assert code == ""


@execute
def demo_assignments():
test = 0
test = TEST_STRING
test = ()
test = []
test = {}
test = True
test = False
test = None
tmp = [0, TEST_STRING, (), [], {}]
test = tmp
del test, tmp

# Augmented Assignments
test = 0
test += 1
test -= 1
test /= 1
test *= 1
test >>= 1
test <<= 1
test **= 1
test |= 1
test &= 1
test %= 1
del test


@execute
def demo_functions():
test = [0, 1, 3, 4]
tmp = test[0:2]
end = test.pop()
begin = test.shift()
test.unshift(begin)
test.append(end)
test.splice(1, 2, 3, 3)
test = "aba"
test.split("b")
test.substr(1, 2)


@execute
def demo_blocks():
test = []
i = 1
while i <= 10:
test.append(i)
i += 1

if len(test) == 10:
return "ok"
elif i == 1:
return "oops..."
else:
return "oops..."


def test_converter():
assignments = demo_assignments.build()
functions = demo_functions.build()
blocks = demo_blocks.build()
assert assignments == (
"var test=0;"
f"var test={repr(TEST_STRING)};"
"var test=[];"
"var test=[];"
"var test={};"
"var test=true;"
"var test=false;"
"var test=null;"
f"var tmp=[0,{repr(TEST_STRING)},[],[],{{}}];"
"var test=tmp;"
"delete test;"
"delete tmp;"
"var test=0;"
"test=test+(1);"
"test=test-(1);"
"test=test/(1);"
"test=test*(1);"
"test=test>>(1);"
"test=test<<(1);"
"test=test**(1);"
"test=test|(1);"
"test=test&(1);"
"test=test%(1);"
"delete test;"
)
assert functions == (
"var test=[0,1,3,4];"
"var tmp=test.slice(0,2);"
"var end=test.pop();"
"var begin=test.shift();"
"test.unshift(begin);"
"test.push(end);"
"test.splice(1,2,3,3);"
"var test='aba';"
"test.split('b');"
"test.substr(1,2);"
)
assert blocks == (
"var test=[];"
"var i=1;"
"while(i<=10){"
"test.push(i);"
"i=i+(1);"
"};"
"if(test.length==10){"
"return 'ok';"
"}else{"
"if(i==1){"
"return 'oops...';"
"}else{"
"return 'oops...';"
"};"
"};"
)


@execute
def demo_args(x, y):
i = x
i *= y
return i


@execute
def demo_preprocessor(x, y):
return x + y * x


@demo_preprocessor.preprocessor
async def preprocessor(x, y):
return demo_preprocessor.build(x, y + 1)


@pytest.mark.asyncio
async def test_execute():
assert demo_args.build(10, 20) == "var i=10;i=i*(20);return i;"
assert await demo_preprocessor(7, 3) == "return 7+4*7;"
2 changes: 2 additions & 0 deletions vkwave/api/methods/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .board import Board
from .database import Database
from .docs import Docs
from .execute import Execute
from .fave import Fave
from .friends import Friends
from .gifts import Gifts
Expand Down Expand Up @@ -96,6 +97,7 @@ def __init__(self, api_options: APIOptions):
self.board = Board("board", self)
self.database = Database("database", self)
self.docs = Docs("docs", self)
self.execute = Execute("execute", self)
self.fave = Fave("fave", self)
self.friends = Friends("friends", self)
self.gifts = Gifts("gifts", self)
Expand Down
13 changes: 13 additions & 0 deletions vkwave/api/methods/execute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ._category import Category
from vkwave.types.responses import ExecuteResponse


class Execute(Category):
async def __call__(self, code: str, return_raw_response: bool = False):

raw_result = await self.api_request("", {"code": code})
if return_raw_response:
return raw_result

result = ExecuteResponse(**raw_result)
return result
4 changes: 4 additions & 0 deletions vkwave/types/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4033,3 +4033,7 @@ class StoriesSearchResponseModel(pydantic.BaseModel):
groups: typing.List[GroupsGroup] = pydantic.Field(
None, description="",
)


class ExecuteResponse(pydantic.BaseModel):
response: typing.Any = pydantic.Field(..., description="")
7 changes: 7 additions & 0 deletions vkwave/vkscript/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import vkwave.vkscript.handlers.statements
from .converter import VKScriptConverter
from .execute import Execute
from .execute import execute


__all__ = ("execute", "Execute", "VKScriptConverter")
58 changes: 58 additions & 0 deletions vkwave/vkscript/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import typing
import contextvars

import pydantic

from typing import Type
from typing import TypeVar


T = TypeVar("T")


class ContextInstanceMixin:
def __init_subclass__(cls, **kwargs):
cls.__context_instance = contextvars.ContextVar("instance_" + cls.__name__)
return cls

@classmethod
def get_current(cls: Type[T], no_error=True) -> T:
if no_error:
return cls.__context_instance.get(None)
return cls.__context_instance.get()

@classmethod
def set_current(cls: Type[T], value: T):
if not isinstance(value, cls):
raise TypeError(
f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'"
)
cls.__context_instance.set(value)


class Scope(pydantic.BaseModel):
locals: list = []
globals: dict = {}


class VKScriptConverter(ContextInstanceMixin):
handlers: dict = {}

@classmethod
def register(cls, expr):
def meta(handler: typing.Callable):
cls.handlers[expr] = handler

return meta

def __init__(self, scope: Scope = None):
self.scope = scope or Scope()
self.set_current(self)

def convert_node(self, node):
if node.__class__ in self.handlers:
return self.handlers[node.__class__](node)
raise NotImplementedError(f"Conversion for type {node.__class__} not implemented.")

def convert_block(self, nodes: list):
return "".join(self.convert_node(child) for child in nodes)
55 changes: 55 additions & 0 deletions vkwave/vkscript/execute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import ast
import inspect
import types

from vkwave.api.methods import APIOptionsRequestContext
from vkwave.vkscript import Scope
from vkwave.vkscript import VKScriptConverter


def execute(func: types.FunctionType):
e = Execute()
return e.decorate(func)


class Execute:
_code = None
_preprocessor = None
_func = None

def decorate(self, func):
source = inspect.getsource(func)
self._func = func
self._code = ast.parse(source).body[0]
return self

def preprocessor(self, func):
self._preprocessor = func

def build(self, *args, **kwargs) -> str:
if self._code.__class__ == ast.FunctionDef:
globals_ = dict(self._func.__globals__)
for i, argument in enumerate(self._code.args.args):
if argument.arg in kwargs:
globals_[argument.arg] = kwargs[argument.arg]
elif i < len(args):
globals_[argument.arg] = args[i]
elif argument.arg.upper() == "API":
continue
else:
raise TypeError(f"missing required argument {argument.arg}")
converter = VKScriptConverter(Scope(globals=globals_))
return converter.convert_block(self._code.body)
raise NotImplementedError()

async def __call__(self, *args, **kwargs):
if self._preprocessor is not None:
return await self._preprocessor(*args, **kwargs)
return await self.execute(*args, **kwargs)

async def execute(self, api: APIOptionsRequestContext, *args, **kwargs):
code = self.build(*args, **kwargs)
response = await api.execute(code=code)
return response

e = execute
Empty file.
Loading

0 comments on commit 185be45

Please sign in to comment.