omlish 0.0.0.dev103__py3-none-any.whl → 0.0.0.dev104__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev103'
2
- __revision__ = 'e402b674aa19d5ca77d769f4290c26e35eb24c22'
1
+ __version__ = '0.0.0.dev104'
2
+ __revision__ = '1ffe2eb6d42217c0ee8b049c6c3b04fd970f9e66'
3
3
 
4
4
 
5
5
  #
@@ -94,3 +94,6 @@ class GreenletThreadlets(Threadlets):
94
94
 
95
95
  def get_current(self) -> Threadlet:
96
96
  return GreenletThreadlet(greenlet.getcurrent())
97
+
98
+
99
+ GREENLET_THREADLETS = GreenletThreadlets()
omlish/fnpairs.py CHANGED
@@ -26,7 +26,6 @@ from . import lang
26
26
  if ta.TYPE_CHECKING:
27
27
  import bz2 as _bz2
28
28
  import gzip as _gzip
29
- import json as _json
30
29
  import lzma as _lzma
31
30
  import pickle as _pickle
32
31
  import struct as _struct
@@ -40,10 +39,11 @@ if ta.TYPE_CHECKING:
40
39
  import yaml as _yaml
41
40
  import zstandard as _zstandard
42
41
 
42
+ from .formats import json as _json
43
+
43
44
  else:
44
45
  _bz2 = lang.proxy_import('bz2')
45
46
  _gzip = lang.proxy_import('gzip')
46
- _json = lang.proxy_import('json')
47
47
  _lzma = lang.proxy_import('lzma')
48
48
  _pickle = lang.proxy_import('pickle')
49
49
  _struct = lang.proxy_import('struct')
@@ -57,6 +57,8 @@ else:
57
57
  _yaml = lang.proxy_import('yaml')
58
58
  _zstandard = lang.proxy_import('zstandard')
59
59
 
60
+ _json = lang.proxy_import('.formats.json', __package__)
61
+
60
62
 
61
63
  ##
62
64
 
@@ -277,7 +279,7 @@ def _register_extension(*ss):
277
279
  def inner(cls):
278
280
  for s in ss:
279
281
  if s in _EXTENSION_REGISTRY:
280
- raise Exception(s)
282
+ raise KeyError(s)
281
283
  _EXTENSION_REGISTRY[s] = cls
282
284
  return cls
283
285
  return inner
@@ -409,22 +411,30 @@ class Pickle(ObjectBytes_):
409
411
  return _pickle.loads(t)
410
412
 
411
413
 
414
+ class _Json(ObjectStr_, lang.Abstract): # noqa
415
+ def backward(self, t: str) -> ta.Any:
416
+ return _json.loads(t)
417
+
418
+
412
419
  @_register_extension('json')
413
- @dc.dataclass(frozen=True)
414
- class Json(ObjectStr_):
415
- indent: int | str | None = dc.field(default=None, kw_only=True)
416
- separators: tuple[str, str] | None = dc.field(default=None, kw_only=True)
420
+ class Json(_Json):
421
+ def forward(self, f: ta.Any) -> str:
422
+ return _json.dumps(f)
423
+
417
424
 
425
+ class JsonPretty(_Json):
418
426
  def forward(self, f: ta.Any) -> str:
419
- return _json.dumps(f, indent=self.indent, separators=self.separators)
427
+ return _json.dumps_pretty(f)
420
428
 
421
- def backward(self, t: str) -> ta.Any:
422
- return _json.loads(t)
429
+
430
+ class JsonCompact(_Json):
431
+ def forward(self, f: ta.Any) -> str:
432
+ return _json.dumps_compact(f)
423
433
 
424
434
 
425
435
  JSON = Json()
426
- PRETTY_JSON = Json(indent=2)
427
- COMPACT_JSON = Json(separators=(',', ':'))
436
+ PRETTY_JSON = JsonPretty()
437
+ COMPACT_JSON = JsonCompact()
428
438
 
429
439
 
430
440
  @_register_extension('jsonl')
@@ -1,7 +1,41 @@
1
1
  """
2
2
  TODO:
3
- - -I/-Ogz, lz4, etc - fnpairs
4
3
  - read from http
4
+ - jmespath output flat, unquoted strs like jq '.[]'
5
+
6
+ ==
7
+
8
+ jq Command options:
9
+ -n, --null-input use `null` as the single input value;
10
+ -R, --raw-input read each line as string instead of JSON;
11
+ -s, --slurp read all inputs into an array and use it as the single input value;
12
+ -c, --compact-output compact instead of pretty-printed output;
13
+ -r, --raw-output output strings without escapes and quotes;
14
+ --raw-output0 implies -r and output NUL after each output;
15
+ -j, --join-output implies -r and output without newline after each output;
16
+ -a, --ascii-output output strings by only ASCII characters using escape sequences;
17
+ -S, --sort-keys sort keys of each object on output;
18
+ -C, --color-output colorize JSON output;
19
+ -M, --monochrome-output disable colored output;
20
+ --tab use tabs for indentation;
21
+ --indent n use n spaces for indentation (max 7 spaces);
22
+ --unbuffered flush output stream after each output;
23
+ --stream parse the input value in streaming fashion;
24
+ --stream-errors implies --stream and report parse error as an array;
25
+ --seq parse input/output as application/json-seq;
26
+ -f, --from-file file load filter from the file;
27
+ -L directory search modules from the directory;
28
+ --arg name value set $name to the string value;
29
+ --argjson name value set $name to the JSON value;
30
+ --slurpfile name file set $name to an array of JSON values read from the file;
31
+ --rawfile name file set $name to string contents of file;
32
+ --args consume remaining arguments as positional string values;
33
+ --jsonargs consume remaining arguments as positional JSON values;
34
+ -e, --exit-status set exit status code based on the output;
35
+ -V, --version show the version;
36
+ --build-configuration show jq's build configuration;
37
+ -h, --help show the help;
38
+ -- terminates argument processing;
5
39
  """
6
40
  import argparse
7
41
  import codecs
@@ -44,12 +44,19 @@ class Format:
44
44
 
45
45
  class Formats(enum.Enum):
46
46
  JSON = Format(['json'], json.load)
47
+
47
48
  YAML = Format(['yaml', 'yml'], lambda f: yaml.safe_load(f))
49
+
48
50
  TOML = Format(['toml'], lambda f: tomllib.loads(f.read()))
51
+
49
52
  ENV = Format(['env', 'dotenv'], lambda f: dotenv.dotenv_values(stream=f))
53
+
50
54
  PROPS = Format(['properties', 'props'], lambda f: dict(props.Properties().load(f.read())))
55
+
51
56
  PY = Format(['py', 'python', 'repr'], lambda f: ast.literal_eval(f.read()))
57
+
52
58
  XML = Format(['xml'], lambda f: xml.build_simple_element(xml.parse_tree(f.read()).getroot()).as_dict())
59
+
53
60
  CSV = Format(['csv'], lambda f: list(csv.DictReader(f)))
54
61
  TSV = Format(['tsv'], lambda f: list(csv.DictReader(f, delimiter='\t')))
55
62
  FLAT_CSV = Format(['fcsv'], lambda f: list(csv.reader(f)))
@@ -0,0 +1,74 @@
1
+ # """
2
+ # TODO:
3
+ # - -I/-Ogz, lz4, etc
4
+ # - fnpairs? or not yet just do it
5
+ # """
6
+ # import abc
7
+ # import contextlib
8
+ # import dataclasses as dc
9
+ # import gzip
10
+ # import os
11
+ # import typing as ta
12
+ #
13
+ # from .... import lang
14
+ #
15
+ #
16
+ # if ta.TYPE_CHECKING:
17
+ # import bz2 as _bz2
18
+ # import gzip as _gzip
19
+ # import lzma as _lzma
20
+ # else:
21
+ # _bz2 = lang.proxy_import('bz2')
22
+ # _gzip = lang.proxy_import('gzip')
23
+ # _lzma = lang.proxy_import('lzma')
24
+ #
25
+ #
26
+ # ##
27
+ #
28
+ #
29
+ # class BaseIo(lang.Abstract):
30
+ # def close(self) -> None:
31
+ # raise NotImplementedError
32
+ #
33
+ # def fileno(self) -> int | None:
34
+ # return None
35
+ #
36
+ #
37
+ # class Input(BaseIo):
38
+ # @abc.abstractmethod
39
+ # def read(self, sz: int | None = None) -> bytes:
40
+ # raise NotImplementedError
41
+ #
42
+ #
43
+ # class Output(BaseIo):
44
+ # @abc.abstractmethod
45
+ # def write(self, data: bytes) -> int:
46
+ # raise NotImplementedError
47
+ #
48
+ #
49
+ # #
50
+ #
51
+ #
52
+ # DEFAULT_READ_SZ = 0x4000
53
+ #
54
+ #
55
+ # @dc.dataclass(frozen=True)
56
+ # class FdIo(Input, Output):
57
+ # fd: int
58
+ #
59
+ # default_read_sz: int = DEFAULT_READ_SZ
60
+ #
61
+ # def read(self, sz: int | None = None) -> bytes:
62
+ # return os.read(self.fd, sz or self.default_read_sz)
63
+ #
64
+ # def write(self, data: bytes) -> int:
65
+ # return os.write(self.fd, data)
66
+ #
67
+ #
68
+ # ##
69
+ #
70
+ #
71
+ # @contextlib.contextmanager
72
+ # def gzip_io_codec(f: ta.IO, mode: str) -> ta.ContextManager[ta.IO]:
73
+ # with gzip.open(f, mode) as o:
74
+ # yield o
omlish/http/consts.py CHANGED
@@ -42,6 +42,8 @@ CONTENT_CHARSET_UTF8 = b'charset=utf-8'
42
42
 
43
43
  CONTENT_TYPE_BYTES = b'application/octet-stream'
44
44
 
45
+ CONTENT_TYPE_FORM_URLENCODED = b'application/x-www-form-urlencoded'
46
+
45
47
  CONTENT_TYPE_HTML = b'text/html'
46
48
  CONTENT_TYPE_HTML_UTF8 = b'; '.join([CONTENT_TYPE_HTML, CONTENT_CHARSET_UTF8])
47
49
 
omlish/http/jwt.py ADDED
@@ -0,0 +1,179 @@
1
+ import abc
2
+ import base64
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ import typing as ta
7
+
8
+ from .. import lang
9
+
10
+
11
+ if ta.TYPE_CHECKING:
12
+ import cryptography.hazmat.primitives.asymmetric.padding
13
+ import cryptography.hazmat.primitives.hashes
14
+ import cryptography.hazmat.primitives.serialization
15
+ else:
16
+ cryptography = lang.proxy_import('cryptography', extras=[
17
+ 'hazmat.primitives.asymmetric.padding',
18
+ 'hazmat.primitives.hashes',
19
+ 'hazmat.primitives.serialization',
20
+ ])
21
+
22
+
23
+ ##
24
+
25
+
26
+ def as_bytes(v: str | bytes, encoding: str = 'utf-8') -> bytes:
27
+ return v.encode(encoding) if isinstance(v, str) else v
28
+
29
+
30
+ def base64url_encode(b: bytes) -> bytes:
31
+ return base64.urlsafe_b64encode(b).replace(b'=', b'')
32
+
33
+
34
+ ##
35
+
36
+
37
+ class Algorithm(abc.ABC):
38
+ @property
39
+ @abc.abstractmethod
40
+ def name(self) -> str:
41
+ raise NotImplementedError
42
+
43
+ @abc.abstractmethod
44
+ def prepare_key(self, key: str | bytes) -> ta.Any:
45
+ raise NotImplementedError
46
+
47
+ @abc.abstractmethod
48
+ def sign(self, msg: str | bytes, key: ta.Any) -> bytes:
49
+ raise NotImplementedError
50
+
51
+
52
+ class HmacAlgorithm(Algorithm):
53
+ def __init__(self, name: str, digest: ta.Any) -> None:
54
+ super().__init__()
55
+ self._name = name
56
+ self._digest = digest
57
+
58
+ @property
59
+ def name(self) -> str:
60
+ return self._name
61
+
62
+ def prepare_key(self, key: str | bytes) -> ta.Any:
63
+ return as_bytes(key)
64
+
65
+ def sign(self, msg: str | bytes, key: ta.Any) -> bytes:
66
+ return hmac.new(key, as_bytes(msg), self._digest).digest()
67
+
68
+
69
+ class RsaAlgorithm(Algorithm):
70
+ def __init__(self, name: str, digest: str) -> None:
71
+ super().__init__()
72
+ self._name = name
73
+ self._digest = digest
74
+
75
+ @property
76
+ def name(self) -> str:
77
+ return self._name
78
+
79
+ def prepare_key(self, key: str | bytes) -> ta.Any:
80
+ if (key_bytes := as_bytes(key)).startswith(b'ssh-rsa'):
81
+ return cryptography.hazmat.primitives.serialization.load_ssh_public_key(key_bytes)
82
+ else:
83
+ return cryptography.hazmat.primitives.serialization.load_pem_private_key(key_bytes, password=None)
84
+
85
+ def sign(self, msg: str | bytes, key: ta.Any) -> bytes:
86
+ return key.sign(
87
+ as_bytes(msg),
88
+ cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15(),
89
+ getattr(cryptography.hazmat.primitives.hashes, self._digest)(),
90
+ )
91
+
92
+
93
+ ALGORITHMS_BY_NAME = {
94
+ a.name: a for a in [
95
+ HmacAlgorithm('HS256', hashlib.sha256),
96
+ HmacAlgorithm('HS384', hashlib.sha384),
97
+ HmacAlgorithm('HS512', hashlib.sha512),
98
+
99
+ RsaAlgorithm('RS256', 'SHA256'),
100
+ RsaAlgorithm('RS384', 'SHA384'),
101
+ RsaAlgorithm('RS512', 'SHA512'),
102
+ ]
103
+ }
104
+
105
+
106
+ ##
107
+
108
+
109
+ def jwt_encode(
110
+ payload: ta.Mapping[str, ta.Any],
111
+ key: str | bytes,
112
+ *,
113
+ algorithm: str = 'HS256',
114
+ ) -> str:
115
+ alg = ALGORITHMS_BY_NAME[algorithm]
116
+
117
+ segments: list[bytes] = []
118
+
119
+ header: dict[str, ta.Any] = {
120
+ 'typ': 'jwt',
121
+ 'alg': alg.name,
122
+ }
123
+ json_header = json.dumps(header, separators=(',', ':'), sort_keys=True).encode('utf-8')
124
+ segments.append(base64url_encode(json_header))
125
+
126
+ json_payload = json.dumps(payload, separators=(',', ':')).encode('utf-8')
127
+ msg_payload = base64url_encode(json_payload)
128
+ segments.append(msg_payload)
129
+
130
+ signing_input = b'.'.join(segments)
131
+
132
+ key = alg.prepare_key(key)
133
+ signature = alg.sign(signing_input, key)
134
+
135
+ segments.append(base64url_encode(signature))
136
+
137
+ encoded_string = b'.'.join(segments)
138
+ return encoded_string.decode('utf-8')
139
+
140
+
141
+ def generate_jwt(
142
+ *,
143
+ issuer: str,
144
+ subject: str,
145
+ audience: str,
146
+ issued_at: int,
147
+ expires_at: int,
148
+ scope: str,
149
+ key: str | bytes,
150
+ **kwargs: ta.Any,
151
+ ) -> str:
152
+ payload = {
153
+ 'iss': issuer,
154
+ 'sub': subject,
155
+ 'aud': audience,
156
+ 'iat': issued_at,
157
+ 'exp': expires_at,
158
+ 'scope': scope,
159
+ }
160
+
161
+ return jwt_encode(
162
+ payload,
163
+ key,
164
+ **kwargs,
165
+ )
166
+
167
+
168
+ ##
169
+
170
+
171
+ SERVICE_APPLICATION_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
172
+
173
+
174
+ def build_get_token_body(
175
+ signed_jwt: str,
176
+ *,
177
+ grant_type: str = SERVICE_APPLICATION_GRANT_TYPE,
178
+ ) -> str:
179
+ return f'grant_type={grant_type}&assertion={signed_jwt}'
omlish/io/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from ..lite.io import ( # noqa
2
+ DelimitingBuffer,
3
+ )