rapydscript-ns 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/PYTHON_GAPS.md +352 -0
- package/README.md +176 -32
- package/TODO.md +1 -128
- package/bin/rapydscript +70 -70
- package/language-service/index.js +242 -11
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/src/ast.pyj +10 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +25 -1
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +18 -5
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +228 -4
- package/src/lib/csv.pyj +494 -0
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/logging.pyj +672 -0
- package/src/lib/pprint.pyj +455 -0
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/statistics.pyj +0 -0
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +203 -4
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/codegen.pyj +4 -1
- package/src/output/functions.pyj +152 -6
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/parse.pyj +108 -24
- package/src/tokenizer.pyj +19 -3
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/baselib.pyj +23 -0
- package/test/bisect.pyj +178 -0
- package/test/chainmap.pyj +185 -0
- package/test/csv.pyj +405 -0
- package/test/float_special.pyj +64 -0
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/parenthesized_with.pyj +141 -0
- package/test/pprint.pyj +232 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/statistics.pyj +224 -0
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +94 -6
- package/test/unit/language-service-completions.js +121 -0
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +190 -5
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2401 -13
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/embedded_compiler.js +7 -7
- package/tools/export.js +4 -2
- package/web-repl/main.js +1 -1
- package/web-repl/rapydscript.js +7 -5
- package/test/omit_function_metadata.pyj +0 -20
package/test/html.pyj
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# html.pyj
|
|
5
|
+
# Tests for the html standard library module.
|
|
6
|
+
|
|
7
|
+
from html import escape, unescape, HTMLParser
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
|
|
13
|
+
# ── 1. escape – individual special characters ─────────────────────────────
|
|
14
|
+
ae(escape('<'), '<')
|
|
15
|
+
ae(escape('>'), '>')
|
|
16
|
+
ae(escape('&'), '&')
|
|
17
|
+
ae(escape('"'), '"')
|
|
18
|
+
ae(escape("'"), ''')
|
|
19
|
+
|
|
20
|
+
# ── 2. escape – combined string ───────────────────────────────────────────
|
|
21
|
+
ae(escape('<script>alert("xss")</script>'),
|
|
22
|
+
'<script>alert("xss")</script>')
|
|
23
|
+
|
|
24
|
+
# ── 3. escape – quote=False leaves quotes unchanged ───────────────────────
|
|
25
|
+
ae(escape('A & B', quote=False), 'A & B')
|
|
26
|
+
ae(escape('"quoted"', quote=False), '"quoted"')
|
|
27
|
+
ae(escape("it's", quote=False), "it's")
|
|
28
|
+
|
|
29
|
+
# ── 4. escape – plain text unchanged ──────────────────────────────────────
|
|
30
|
+
ae(escape('hello world'), 'hello world')
|
|
31
|
+
ae(escape(''), '')
|
|
32
|
+
|
|
33
|
+
# ── 5. unescape – named entities ──────────────────────────────────────────
|
|
34
|
+
ae(unescape('&'), '&')
|
|
35
|
+
ae(unescape('<'), '<')
|
|
36
|
+
ae(unescape('>'), '>')
|
|
37
|
+
ae(unescape('"'), '"')
|
|
38
|
+
ae(unescape('''), "'")
|
|
39
|
+
ae(unescape('©'), '©')
|
|
40
|
+
ae(unescape('€'), '€')
|
|
41
|
+
ae(unescape('–'), '–')
|
|
42
|
+
ae(unescape('—'), '—')
|
|
43
|
+
ae(unescape('™'), '™')
|
|
44
|
+
|
|
45
|
+
# ── 6. unescape – numeric references ──────────────────────────────────────
|
|
46
|
+
ae(unescape('A'), 'A') # decimal
|
|
47
|
+
ae(unescape('A'), 'A') # hex lowercase
|
|
48
|
+
ae(unescape('A'), 'A') # hex uppercase
|
|
49
|
+
ae(unescape('©'), '©') # © by decimal
|
|
50
|
+
ae(unescape('€'), '€') # € by hex
|
|
51
|
+
|
|
52
|
+
# ── 7. unescape – high code point (emoji via numeric ref) ─────────────────
|
|
53
|
+
ae(unescape('😀'), chr(128512)) # grinning face emoji
|
|
54
|
+
|
|
55
|
+
# ── 8. unescape – unknown entity left intact ──────────────────────────────
|
|
56
|
+
ae(unescape('&nosuchentity;'), '&nosuchentity;')
|
|
57
|
+
|
|
58
|
+
# ── 9. unescape – mixed string ────────────────────────────────────────────
|
|
59
|
+
ae(unescape('Hello & World <3'), 'Hello & World <3')
|
|
60
|
+
|
|
61
|
+
# ── 10. round-trip ───────────────────────────────────────────────────────
|
|
62
|
+
s = '<a href="http://example.com">Hello & World</a>'
|
|
63
|
+
ae(unescape(escape(s)), s)
|
|
64
|
+
|
|
65
|
+
# ── 11. HTMLParser – start/end/data ──────────────────────────────────────
|
|
66
|
+
class _P1(HTMLParser):
|
|
67
|
+
def __init__(self):
|
|
68
|
+
HTMLParser.__init__(self)
|
|
69
|
+
self.events = []
|
|
70
|
+
def handle_starttag(self, tag, attrs):
|
|
71
|
+
self.events.push(['start', tag, attrs])
|
|
72
|
+
def handle_endtag(self, tag):
|
|
73
|
+
self.events.push(['end', tag])
|
|
74
|
+
def handle_data(self, data):
|
|
75
|
+
self.events.push(['data', data])
|
|
76
|
+
|
|
77
|
+
p1 = _P1()
|
|
78
|
+
p1.feed('<p class="greeting">Hello, World!</p>')
|
|
79
|
+
ae(p1.events[0][0], 'start')
|
|
80
|
+
ae(p1.events[0][1], 'p')
|
|
81
|
+
ae(p1.events[0][2][0][0], 'class')
|
|
82
|
+
ae(p1.events[0][2][0][1], 'greeting')
|
|
83
|
+
ae(p1.events[1][0], 'data')
|
|
84
|
+
ae(p1.events[1][1], 'Hello, World!')
|
|
85
|
+
ae(p1.events[2][0], 'end')
|
|
86
|
+
ae(p1.events[2][1], 'p')
|
|
87
|
+
|
|
88
|
+
# ── 12. HTMLParser – entity auto-conversion in data ───────────────────────
|
|
89
|
+
class _P2(HTMLParser):
|
|
90
|
+
def __init__(self):
|
|
91
|
+
HTMLParser.__init__(self)
|
|
92
|
+
self.chunks = []
|
|
93
|
+
def handle_data(self, data):
|
|
94
|
+
self.chunks.push(data)
|
|
95
|
+
|
|
96
|
+
p2 = _P2()
|
|
97
|
+
p2.feed('<p>Hello & World <3</p>')
|
|
98
|
+
ae(p2.chunks[0], 'Hello & World <3')
|
|
99
|
+
|
|
100
|
+
# ── 13. HTMLParser – comment ──────────────────────────────────────────────
|
|
101
|
+
class _P3(HTMLParser):
|
|
102
|
+
def __init__(self):
|
|
103
|
+
HTMLParser.__init__(self)
|
|
104
|
+
self.comments = []
|
|
105
|
+
def handle_comment(self, data):
|
|
106
|
+
self.comments.push(data)
|
|
107
|
+
|
|
108
|
+
p3 = _P3()
|
|
109
|
+
p3.feed('<!-- this is a comment -->')
|
|
110
|
+
ae(p3.comments.length, 1)
|
|
111
|
+
ae(p3.comments[0], ' this is a comment ')
|
|
112
|
+
|
|
113
|
+
# ── 14. HTMLParser – multiple attributes ─────────────────────────────────
|
|
114
|
+
class _P4(HTMLParser):
|
|
115
|
+
def __init__(self):
|
|
116
|
+
HTMLParser.__init__(self)
|
|
117
|
+
self.attrs = None
|
|
118
|
+
def handle_starttag(self, tag, attrs):
|
|
119
|
+
self.attrs = attrs
|
|
120
|
+
|
|
121
|
+
p4 = _P4()
|
|
122
|
+
p4.feed('<a href="http://example.com" target="_blank" rel="noopener">')
|
|
123
|
+
ae(p4.attrs.length, 3)
|
|
124
|
+
ae(p4.attrs[0][0], 'href')
|
|
125
|
+
ae(p4.attrs[0][1], 'http://example.com')
|
|
126
|
+
ae(p4.attrs[1][0], 'target')
|
|
127
|
+
ae(p4.attrs[1][1], '_blank')
|
|
128
|
+
ae(p4.attrs[2][0], 'rel')
|
|
129
|
+
ae(p4.attrs[2][1], 'noopener')
|
|
130
|
+
|
|
131
|
+
# ── 15. HTMLParser – self-closing tag ─────────────────────────────────────
|
|
132
|
+
class _P5(HTMLParser):
|
|
133
|
+
def __init__(self):
|
|
134
|
+
HTMLParser.__init__(self)
|
|
135
|
+
self.events = []
|
|
136
|
+
def handle_starttag(self, tag, attrs):
|
|
137
|
+
self.events.push('start:' + tag)
|
|
138
|
+
def handle_endtag(self, tag):
|
|
139
|
+
self.events.push('end:' + tag)
|
|
140
|
+
|
|
141
|
+
p5 = _P5()
|
|
142
|
+
p5.feed('<br/>')
|
|
143
|
+
ae(p5.events.length, 2)
|
|
144
|
+
ae(p5.events[0], 'start:br')
|
|
145
|
+
ae(p5.events[1], 'end:br')
|
|
146
|
+
|
|
147
|
+
# ── 16. HTMLParser – get_starttag_text ───────────────────────────────────
|
|
148
|
+
class _P6(HTMLParser):
|
|
149
|
+
def __init__(self):
|
|
150
|
+
HTMLParser.__init__(self)
|
|
151
|
+
self.last_raw = None
|
|
152
|
+
def handle_starttag(self, tag, attrs):
|
|
153
|
+
self.last_raw = self.get_starttag_text()
|
|
154
|
+
|
|
155
|
+
p6 = _P6()
|
|
156
|
+
p6.feed('<img src="pic.png" alt="photo">')
|
|
157
|
+
ae(p6.last_raw, '<img src="pic.png" alt="photo">')
|
|
158
|
+
|
|
159
|
+
# ── 17. HTMLParser – DOCTYPE declaration ─────────────────────────────────
|
|
160
|
+
class _P7(HTMLParser):
|
|
161
|
+
def __init__(self):
|
|
162
|
+
HTMLParser.__init__(self)
|
|
163
|
+
self.decls = []
|
|
164
|
+
def handle_decl(self, decl):
|
|
165
|
+
self.decls.push(decl)
|
|
166
|
+
|
|
167
|
+
p7 = _P7()
|
|
168
|
+
p7.feed('<!DOCTYPE html>')
|
|
169
|
+
ae(p7.decls.length, 1)
|
|
170
|
+
ae(p7.decls[0], 'DOCTYPE html')
|
|
171
|
+
|
|
172
|
+
# ── 18. HTMLParser – tag names lowercased ────────────────────────────────
|
|
173
|
+
class _P8(HTMLParser):
|
|
174
|
+
def __init__(self):
|
|
175
|
+
HTMLParser.__init__(self)
|
|
176
|
+
self.tags = []
|
|
177
|
+
def handle_starttag(self, tag, attrs):
|
|
178
|
+
self.tags.push(tag)
|
|
179
|
+
|
|
180
|
+
p8 = _P8()
|
|
181
|
+
p8.feed('<DIV><SPAN></SPAN></DIV>')
|
|
182
|
+
ae(p8.tags[0], 'div')
|
|
183
|
+
ae(p8.tags[1], 'span')
|
|
184
|
+
|
|
185
|
+
# ── 19. HTMLParser – valueless attribute yields None ──────────────────────
|
|
186
|
+
class _P9(HTMLParser):
|
|
187
|
+
def __init__(self):
|
|
188
|
+
HTMLParser.__init__(self)
|
|
189
|
+
self.attrs = None
|
|
190
|
+
def handle_starttag(self, tag, attrs):
|
|
191
|
+
self.attrs = attrs
|
|
192
|
+
|
|
193
|
+
p9 = _P9()
|
|
194
|
+
p9.feed('<input disabled required>')
|
|
195
|
+
ae(p9.attrs[0][0], 'disabled')
|
|
196
|
+
ae(p9.attrs[0][1], None)
|
|
197
|
+
ae(p9.attrs[1][0], 'required')
|
|
198
|
+
ae(p9.attrs[1][1], None)
|
|
199
|
+
|
|
200
|
+
# ── 20. HTMLParser – incremental feed ────────────────────────────────────
|
|
201
|
+
class _P10(HTMLParser):
|
|
202
|
+
def __init__(self):
|
|
203
|
+
HTMLParser.__init__(self)
|
|
204
|
+
self.data = []
|
|
205
|
+
def handle_data(self, d):
|
|
206
|
+
self.data.push(d)
|
|
207
|
+
|
|
208
|
+
p10 = _P10()
|
|
209
|
+
p10.feed('<p>Hello')
|
|
210
|
+
p10.feed(' World</p>')
|
|
211
|
+
ok(p10.data.length >= 1)
|
|
212
|
+
ae(p10.data.join(''), 'Hello World')
|
package/test/http.pyj
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# http.pyj
|
|
5
|
+
# Tests for the http standard library module.
|
|
6
|
+
#
|
|
7
|
+
# Covers http (HTTPStatus), http.client (pure-JS synchronous parts), and
|
|
8
|
+
# http.cookies. Network-dependent tests (getresponse) are exercised only in
|
|
9
|
+
# the web-repl bundle tests via a mocked fetch environment.
|
|
10
|
+
|
|
11
|
+
from __python__ import overload_getitem
|
|
12
|
+
|
|
13
|
+
from http import HTTPStatus
|
|
14
|
+
from http.client import (HTTPConnection, HTTPSConnection,
|
|
15
|
+
HTTPException, NotConnected, InvalidURL,
|
|
16
|
+
RemoteDisconnected, HTTP_PORT, HTTPS_PORT,
|
|
17
|
+
HTTPResponse)
|
|
18
|
+
from http.cookies import SimpleCookie, Morsel, CookieError
|
|
19
|
+
|
|
20
|
+
ae = assrt.equal
|
|
21
|
+
ade = assrt.deepEqual
|
|
22
|
+
ok = assrt.ok
|
|
23
|
+
|
|
24
|
+
# ── 1. HTTPStatus constants ───────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
ae(HTTPStatus.OK, 200)
|
|
27
|
+
ae(HTTPStatus.CREATED, 201)
|
|
28
|
+
ae(HTTPStatus.NO_CONTENT, 204)
|
|
29
|
+
ae(HTTPStatus.MOVED_PERMANENTLY, 301)
|
|
30
|
+
ae(HTTPStatus.NOT_MODIFIED, 304)
|
|
31
|
+
ae(HTTPStatus.BAD_REQUEST, 400)
|
|
32
|
+
ae(HTTPStatus.UNAUTHORIZED, 401)
|
|
33
|
+
ae(HTTPStatus.FORBIDDEN, 403)
|
|
34
|
+
ae(HTTPStatus.NOT_FOUND, 404)
|
|
35
|
+
ae(HTTPStatus.METHOD_NOT_ALLOWED, 405)
|
|
36
|
+
ae(HTTPStatus.INTERNAL_SERVER_ERROR, 500)
|
|
37
|
+
ae(HTTPStatus.SERVICE_UNAVAILABLE, 503)
|
|
38
|
+
ae(HTTPStatus.IM_A_TEAPOT, 418)
|
|
39
|
+
|
|
40
|
+
# ── 2. Port constants ─────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
ae(HTTP_PORT, 80)
|
|
43
|
+
ae(HTTPS_PORT, 443)
|
|
44
|
+
|
|
45
|
+
# ── 3. HTTPException hierarchy ────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
raise HTTPException('base error')
|
|
49
|
+
ok(False)
|
|
50
|
+
except HTTPException as e:
|
|
51
|
+
ok('base error' in str(e))
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
raise NotConnected('not connected')
|
|
55
|
+
ok(False)
|
|
56
|
+
except HTTPException as e:
|
|
57
|
+
ok(isinstance(e, NotConnected))
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
raise InvalidURL('bad url')
|
|
61
|
+
ok(False)
|
|
62
|
+
except HTTPException as e:
|
|
63
|
+
ok(isinstance(e, InvalidURL))
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
raise RemoteDisconnected('gone')
|
|
67
|
+
ok(False)
|
|
68
|
+
except HTTPException as e:
|
|
69
|
+
ok(isinstance(e, RemoteDisconnected))
|
|
70
|
+
|
|
71
|
+
# ── 4. HTTPConnection — _build_url ────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
conn = HTTPConnection('example.com')
|
|
74
|
+
ae(conn._build_url('/path'), 'http://example.com/path')
|
|
75
|
+
ae(conn._build_url('/'), 'http://example.com/')
|
|
76
|
+
|
|
77
|
+
conn2 = HTTPConnection('example.com', 8080)
|
|
78
|
+
ae(conn2._build_url('/api'), 'http://example.com:8080/api')
|
|
79
|
+
|
|
80
|
+
# Default port 80 should be omitted
|
|
81
|
+
conn3 = HTTPConnection('example.com', 80)
|
|
82
|
+
ae(conn3._build_url('/'), 'http://example.com/')
|
|
83
|
+
|
|
84
|
+
# Full URL passes through unchanged
|
|
85
|
+
ae(conn._build_url('https://other.com/x'), 'https://other.com/x')
|
|
86
|
+
ae(conn._build_url('http://other.com/x'), 'http://other.com/x')
|
|
87
|
+
|
|
88
|
+
# ── 5. HTTPSConnection — _build_url ──────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
sconn = HTTPSConnection('secure.example.com')
|
|
91
|
+
ae(sconn._build_url('/data'), 'https://secure.example.com/data')
|
|
92
|
+
|
|
93
|
+
sconn2 = HTTPSConnection('secure.example.com', 8443)
|
|
94
|
+
ae(sconn2._build_url('/data'), 'https://secure.example.com:8443/data')
|
|
95
|
+
|
|
96
|
+
# Default port 443 should be omitted
|
|
97
|
+
sconn3 = HTTPSConnection('secure.example.com', 443)
|
|
98
|
+
ae(sconn3._build_url('/data'), 'https://secure.example.com/data')
|
|
99
|
+
|
|
100
|
+
# ── 6. HTTPConnection.request stores state ───────────────────────────────────
|
|
101
|
+
|
|
102
|
+
conn4 = HTTPConnection('api.example.com')
|
|
103
|
+
conn4.request('POST', '/items', body='a=1', headers={'Content-Type': 'text/plain'})
|
|
104
|
+
ae(conn4._method, 'POST')
|
|
105
|
+
ae(conn4._path, '/items')
|
|
106
|
+
ae(conn4._body, 'a=1')
|
|
107
|
+
ae(conn4._headers['content-type'], 'text/plain')
|
|
108
|
+
|
|
109
|
+
# ── 7. putrequest / putheader / endheaders ───────────────────────────────────
|
|
110
|
+
|
|
111
|
+
conn5 = HTTPConnection('example.com')
|
|
112
|
+
conn5.putrequest('GET', '/search')
|
|
113
|
+
conn5.putheader('Accept', 'application/json')
|
|
114
|
+
conn5.putheader('X-Custom', 'hello')
|
|
115
|
+
conn5.endheaders()
|
|
116
|
+
ae(conn5._method, 'GET')
|
|
117
|
+
ae(conn5._path, '/search')
|
|
118
|
+
ae(conn5._headers['accept'], 'application/json')
|
|
119
|
+
ae(conn5._headers['x-custom'], 'hello')
|
|
120
|
+
ae(conn5._body, None)
|
|
121
|
+
|
|
122
|
+
conn5b = HTTPConnection('example.com')
|
|
123
|
+
conn5b.putrequest('POST', '/upload')
|
|
124
|
+
conn5b.endheaders('payload')
|
|
125
|
+
ae(conn5b._body, 'payload')
|
|
126
|
+
|
|
127
|
+
# ── 8. HTTPConnection.close resets state ─────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
conn6 = HTTPConnection('example.com')
|
|
130
|
+
conn6.request('DELETE', '/items/1')
|
|
131
|
+
conn6.close()
|
|
132
|
+
ae(conn6._method, None)
|
|
133
|
+
ae(conn6._path, None)
|
|
134
|
+
ae(conn6._body, None)
|
|
135
|
+
|
|
136
|
+
# ── 9. HTTPResponse — sync accessors ─────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
hdrs = {'content-type': 'application/json', 'x-rate-limit': '100'}
|
|
139
|
+
resp = HTTPResponse(200, 'OK', hdrs, '{"ok":true}', 'https://example.com/api')
|
|
140
|
+
ae(resp.status, 200)
|
|
141
|
+
ae(resp.reason, 'OK')
|
|
142
|
+
ae(resp.url, 'https://example.com/api')
|
|
143
|
+
ae(resp.getheader('content-type'), 'application/json')
|
|
144
|
+
ae(resp.getheader('Content-Type'), 'application/json')
|
|
145
|
+
ae(resp.getheader('missing'), None)
|
|
146
|
+
ae(resp.getheader('missing', 'default'), 'default')
|
|
147
|
+
ok(not resp.closed)
|
|
148
|
+
resp.close()
|
|
149
|
+
ok(resp.closed)
|
|
150
|
+
|
|
151
|
+
# ── 10. HTTPResponse.getheaders ───────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
pairs = resp.getheaders()
|
|
154
|
+
ae(len(pairs), 2)
|
|
155
|
+
found = False
|
|
156
|
+
for pair in pairs:
|
|
157
|
+
if pair[0] is 'content-type' and pair[1] is 'application/json':
|
|
158
|
+
found = True
|
|
159
|
+
ok(found)
|
|
160
|
+
|
|
161
|
+
# ── 11. HTTPResponse.read / .json return Promises ────────────────────────────
|
|
162
|
+
|
|
163
|
+
resp2 = HTTPResponse(200, 'OK', {}, '{"n":42}', 'https://x.com/')
|
|
164
|
+
read_p = resp2.read()
|
|
165
|
+
json_p = resp2.json()
|
|
166
|
+
ok(read_p is not None)
|
|
167
|
+
ok(json_p is not None)
|
|
168
|
+
ok(jstype(read_p.then) is 'function')
|
|
169
|
+
ok(jstype(json_p.then) is 'function')
|
|
170
|
+
|
|
171
|
+
# ── 12. SimpleCookie — basic parsing ─────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
c = SimpleCookie()
|
|
174
|
+
c.load('session=abc123; user=alice')
|
|
175
|
+
ok('session' in c.keys())
|
|
176
|
+
ok('user' in c.keys())
|
|
177
|
+
ae(c['session'].value, 'abc123')
|
|
178
|
+
ae(c['user'].value, 'alice')
|
|
179
|
+
|
|
180
|
+
# ── 13. SimpleCookie — set a cookie ──────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
c2 = SimpleCookie()
|
|
183
|
+
c2['token'] = 'xyz789'
|
|
184
|
+
ok('token' in c2.keys())
|
|
185
|
+
ae(c2['token'].value, 'xyz789')
|
|
186
|
+
ae(c2['token'].coded_value, 'xyz789')
|
|
187
|
+
|
|
188
|
+
# ── 14. SimpleCookie — cookie attributes ─────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
c3 = SimpleCookie()
|
|
191
|
+
c3['id'] = '1'
|
|
192
|
+
c3['id']['path'] = '/'
|
|
193
|
+
c3['id']['max-age'] = 3600
|
|
194
|
+
c3['id']['secure'] = True
|
|
195
|
+
ae(c3['id']['path'], '/')
|
|
196
|
+
ae(c3['id']['max-age'], 3600)
|
|
197
|
+
ok(c3['id']['secure'])
|
|
198
|
+
|
|
199
|
+
# ── 15. Morsel.OutputString ───────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
m = Morsel()
|
|
202
|
+
m.set('token', 'abc', 'abc')
|
|
203
|
+
m._attrs['path'] = '/'
|
|
204
|
+
m._attrs['max-age'] = '3600'
|
|
205
|
+
s = m.OutputString()
|
|
206
|
+
ok('token=abc' in s)
|
|
207
|
+
ok('Path=/' in s)
|
|
208
|
+
ok('Max-Age=3600' in s)
|
|
209
|
+
|
|
210
|
+
# ── 16. Morsel.output includes header ────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
out = m.output()
|
|
213
|
+
ok('Set-Cookie: token=abc' in out)
|
|
214
|
+
|
|
215
|
+
out2 = m.output('Set-Cookie')
|
|
216
|
+
ok('Set-Cookie: token=abc' in out2)
|
|
217
|
+
|
|
218
|
+
# ── 17. SimpleCookie.output ───────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
c4 = SimpleCookie()
|
|
221
|
+
c4['a'] = '1'
|
|
222
|
+
c4['b'] = '2'
|
|
223
|
+
full = c4.output()
|
|
224
|
+
ok('Set-Cookie: a=1' in full)
|
|
225
|
+
ok('Set-Cookie: b=2' in full)
|
|
226
|
+
|
|
227
|
+
# ── 18. SimpleCookie constructor with initial data ────────────────────────────
|
|
228
|
+
|
|
229
|
+
c5 = SimpleCookie('x=10; y=20')
|
|
230
|
+
ae(c5['x'].value, '10')
|
|
231
|
+
ae(c5['y'].value, '20')
|
|
232
|
+
|
|
233
|
+
# ── 19. SimpleCookie.keys / values / items ───────────────────────────────────
|
|
234
|
+
|
|
235
|
+
c6 = SimpleCookie()
|
|
236
|
+
c6['p'] = 'hello'
|
|
237
|
+
c6['q'] = 'world'
|
|
238
|
+
ks = c6.keys()
|
|
239
|
+
ok('p' in ks) # ks is a list — 'in' checks array membership
|
|
240
|
+
ok('q' in ks)
|
|
241
|
+
vs = c6.values()
|
|
242
|
+
ae(len(vs), 2)
|
|
243
|
+
itms = c6.items()
|
|
244
|
+
ae(len(itms), 2)
|
|
245
|
+
|
|
246
|
+
# ── 20. CookieError is an Exception ──────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
raise CookieError('bad cookie')
|
|
250
|
+
ok(False)
|
|
251
|
+
except CookieError as e:
|
|
252
|
+
ok('bad cookie' in str(e))
|
|
253
|
+
|
|
254
|
+
# ── 21. set_debuglevel / connect no-op ───────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
conn7 = HTTPConnection('example.com')
|
|
257
|
+
conn7.set_debuglevel(1)
|
|
258
|
+
conn7.connect()
|
|
259
|
+
ok(True)
|
package/test/imports.pyj
CHANGED
|
@@ -1,72 +1,79 @@
|
|
|
1
|
-
# globals:test_path, GLOBAL_SYMBOL, assrt
|
|
2
|
-
from _import_one import toplevel_var, toplevel_func as tf, TopLevel, true_var, false_var, test_other
|
|
3
|
-
from _import_two import (toplevel_var2,
|
|
4
|
-
toplevel_func2, TopLevel2 as TL2)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
eq
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
eq('
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
eq(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
eq(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
eq('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
eq(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
inner
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
eq(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
# globals:test_path, GLOBAL_SYMBOL, assrt
|
|
2
|
+
from _import_one import toplevel_var, toplevel_func as tf, TopLevel, true_var, false_var, test_other
|
|
3
|
+
from _import_two import (toplevel_var2,
|
|
4
|
+
toplevel_func2, TopLevel2 as TL2)
|
|
5
|
+
|
|
6
|
+
# Python-style multi-line parenthesized import with trailing comma
|
|
7
|
+
from _import_one import (
|
|
8
|
+
toplevel_var,
|
|
9
|
+
toplevel_func as tf,
|
|
10
|
+
TopLevel,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def AClass(x):
|
|
14
|
+
return this
|
|
15
|
+
|
|
16
|
+
eq = assrt.equal
|
|
17
|
+
# Test import of top-level variables and callables
|
|
18
|
+
eq(toplevel_var, 'foo')
|
|
19
|
+
eq(tf('x'), 'xtoplevel')
|
|
20
|
+
eq(toplevel_var2, 'foo2')
|
|
21
|
+
eq(toplevel_func2('x'), 'xtoplevel2')
|
|
22
|
+
eq(false_var, undefined)
|
|
23
|
+
eq(test_other, 'other')
|
|
24
|
+
|
|
25
|
+
# Test import of top-level vars in a conditional
|
|
26
|
+
eq('true', true_var)
|
|
27
|
+
|
|
28
|
+
# Test plain imports
|
|
29
|
+
import _import_one
|
|
30
|
+
eq(_import_one.toplevel_var, toplevel_var)
|
|
31
|
+
eq(_import_one.toplevel_func('x'), tf('x'))
|
|
32
|
+
|
|
33
|
+
# Test recognition of imported classes
|
|
34
|
+
tl = TopLevel('1')
|
|
35
|
+
eq(tl.a, '1')
|
|
36
|
+
tl2 = TL2('x')
|
|
37
|
+
eq(tl2.a, 'x')
|
|
38
|
+
|
|
39
|
+
# Test access to submodules via plain imports
|
|
40
|
+
import _import_two.sub, _import_two.sub as ts
|
|
41
|
+
eq('sub', _import_two.sub.sub_var)
|
|
42
|
+
eq('sub', ts.sub_var)
|
|
43
|
+
eq('sub', _import_two.sub.sub_func())
|
|
44
|
+
|
|
45
|
+
# Test deep import
|
|
46
|
+
from _import_two.level2.deep import deep_var
|
|
47
|
+
eq('deep', deep_var)
|
|
48
|
+
|
|
49
|
+
# Test that class accessed via plain import is
|
|
50
|
+
# recognized
|
|
51
|
+
s = _import_two.sub.Sub(1)
|
|
52
|
+
eq(s.a, 1)
|
|
53
|
+
s2 = ts.Sub(1)
|
|
54
|
+
eq(s2.a, 1)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Test that a class imported into an inner scope is not recognized as a class
|
|
58
|
+
# outside that scope
|
|
59
|
+
def inner():
|
|
60
|
+
from _import_one import AClass
|
|
61
|
+
a = AClass(1)
|
|
62
|
+
eq(a.a, 1)
|
|
63
|
+
|
|
64
|
+
inner()
|
|
65
|
+
b = AClass(1)
|
|
66
|
+
eq(b, this)
|
|
67
|
+
|
|
68
|
+
# Test global symbol declared in other module
|
|
69
|
+
eq(GLOBAL_SYMBOL, 'i am global')
|
|
70
|
+
|
|
71
|
+
# Import errors happen during parsing, so we cannot test them directly as they would
|
|
72
|
+
# prevent this file from being parsed.
|
|
73
|
+
|
|
74
|
+
assrt.throws(def():
|
|
75
|
+
RapydScript.parse('from _import_one import not_exported', {'basedir':test_path}).body[0]
|
|
76
|
+
, /not exported/)
|
|
77
|
+
assrt.throws(def():
|
|
78
|
+
RapydScript.parse('import xxxx', {'basedir':test_path}).body[0]
|
|
79
|
+
, /doesn't exist/)
|