Răsfoiți Sursa

add `requests` module

blueloveTH 2 ani în urmă
părinte
comite
a04cdb4cad
6 a modificat fișierele cu 155 adăugiri și 7 ștergeri
  1. 1 0
      .gitignore
  2. 1 1
      amalgamate.py
  3. 1 1
      python/_dict.py
  4. 40 0
      python/requests.py
  5. 7 5
      src/pocketpy.h
  6. 105 0
      src/requests.h

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ src/_generated.h
 profile.sh
 test
 tmp.rar
+src/httplib.h

+ 1 - 1
amalgamate.py

@@ -9,7 +9,7 @@ pipeline = [
 	["common.h", "memory.h", "vector.h", "str.h", "tuplelist.h", "namedict.h", "error.h", "lexer.h"],
 	["obj.h", "codeobject.h", "frame.h"],
 	["gc.h", "vm.h", "ceval.h", "expr.h", "compiler.h", "repl.h"],
-	["iter.h", "cffi.h", "io.h", "_generated.h", "pocketpy.h"]
+	["iter.h", "cffi.h", "requests.h", "io.h", "_generated.h", "pocketpy.h"]
 ]
 
 copied = set()

+ 1 - 1
python/_dict.py

@@ -74,7 +74,7 @@ class dict:
     def items(self):
         for kv in self._a:
             if kv is not None:
-                yield kv
+                yield kv[0], kv[1]
 
     def clear(self):
         self._a = [None] * self._capacity

+ 40 - 0
python/requests.py

@@ -0,0 +1,40 @@
+class Response:
+    def __init__(self, status_code, reason, content):
+        self.status_code = status_code
+        self.reason = reason
+        self.content = content
+
+        assert type(self.status_code) is int
+        assert type(self.reason) is str
+        assert type(self.content) is bytes
+
+    @property
+    def text(self):
+        return self.content.decode()
+    
+    def __repr__(self):
+        code = self.status_code
+        return f'<Response [{code}]>'
+
+def _parse_h(headers):
+    if headers is None:
+        return []
+    if type(headers) is dict:
+        return list(headers.items())
+    raise ValueError('headers must be dict or None')
+
+def get(url, headers=None):
+    headers = _parse_h(headers)
+    return _request('GET', url, headers, None)
+
+def post(url, data: bytes, headers=None):
+    headers = _parse_h(headers)
+    return _request('POST', url, headers, data)
+
+def put(url, data: bytes, headers=None):
+    headers = _parse_h(headers)
+    return _request('PUT', url, headers, data)
+
+def delete(url, headers=None):
+    headers = _parse_h(headers)
+    return _request('DELETE', url, headers, None)

+ 7 - 5
src/pocketpy.h

@@ -6,6 +6,7 @@
 #include "repl.h"
 #include "iter.h"
 #include "cffi.h"
+#include "requests.h"
 #include "io.h"
 #include "_generated.h"
 
@@ -920,11 +921,6 @@ inline void VM::post_init(){
     add_module_gc(this);
     add_module_random(this);
 
-    if(enable_os){
-        add_module_io(this);
-        add_module_os(this);
-    }
-
     for(const char* name: {"this", "functools", "collections", "heapq", "bisect"}){
         _lazy_modules[name] = kPythonLibs[name];
     }
@@ -969,6 +965,12 @@ inline void VM::post_init(){
         }
         return VAR(MappingProxy(args[0]));
     }));
+
+    if(enable_os){
+        add_module_io(this);
+        add_module_os(this);
+        add_module_requests(this);
+    }
 #endif
 }
 

+ 105 - 0
src/requests.h

@@ -0,0 +1,105 @@
+#pragma once
+
+#include "common.h"
+#include "obj.h"
+#include "vm.h"
+#include "_generated.h"
+
+#if __has_include("httplib.h")
+#include "httplib.h"
+
+namespace pkpy {
+
+inline void add_module_requests(VM* vm){
+    static StrName m_requests("requests");
+    static StrName m_Response("Response");
+    PyObject* mod = vm->new_module(m_requests);
+    CodeObject_ code = vm->compile(kPythonLibs["requests"], "requests.py", EXEC_MODE);
+    vm->_exec(code, mod);
+
+    vm->bind_func<4>(mod, "_request", [](VM* vm, ArgsView args){
+        Str method = CAST(Str&, args[0]);
+        Str url = CAST(Str&, args[1]);
+        PyObject* headers = args[2];            // a dict object
+        PyObject* body = args[3];               // a bytes object
+
+        if(url.index("http://") != 0){
+            vm->ValueError("url must start with http://");
+        }
+
+        for(char c: url){
+            switch(c){
+                case '.':
+                case '-':
+                case '_':
+                case '~':
+                case ':':
+                case '/':
+                break;
+                default:
+                    if(!isalnum(c)){
+                        vm->ValueError(fmt("invalid character in url: '", c, "'"));
+                    }
+            }
+        }
+
+        int slash = url.index("/", 7);
+        Str path = "/";
+        if(slash != -1){
+            path = url.substr(slash);
+            url = url.substr(0, slash);
+            if(path.empty()) path = "/";
+        }
+
+        httplib::Client client(url.str());
+
+        httplib::Headers h;
+        if(headers != vm->None){
+            List list = CAST(List&, headers);
+            for(auto& item : list){
+                Tuple t = CAST(Tuple&, item);
+                Str key = CAST(Str&, t[0]);
+                Str value = CAST(Str&, t[1]);
+                h.emplace(key.str(), value.str());
+            }
+        }
+
+        auto _to_resp = [=](const httplib::Result& res){
+            std::vector<char> buf(res->body.size());
+            for(int i=0; i<res->body.size(); i++) buf[i] = res->body[i];
+            return vm->call(
+                vm->_modules[m_requests]->attr(m_Response),
+                VAR(res->status),
+                VAR(res->reason),
+                VAR(Bytes(std::move(buf)))
+            );
+        };
+
+        if(method == "GET"){
+            httplib::Result res = client.Get(path.str(), h);
+            return _to_resp(res);
+        }else if(method == "POST"){
+            Bytes b = CAST(Bytes&, body);
+            httplib::Result res = client.Post(path.str(), h, b.data(), b.size(), "application/octet-stream");
+            return _to_resp(res);
+        }else if(method == "PUT"){
+            Bytes b = CAST(Bytes&, body);
+            httplib::Result res = client.Put(path.str(), h, b.data(), b.size(), "application/octet-stream");
+            return _to_resp(res);
+        }else if(method == "DELETE"){
+            httplib::Result res = client.Delete(path.str(), h);
+            return _to_resp(res);
+        }else{
+            vm->ValueError("invalid method");
+        }
+        UNREACHABLE();
+    });
+}
+
+}   // namespace pkpy
+
+#else
+
+inline void add_module_requests(VM* vm){ }
+
+#endif