omlish 0.0.0.dev23__py3-none-any.whl → 0.0.0.dev24__py3-none-any.whl
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.
- omlish/__about__.py +2 -2
- omlish/bootstrap/diag.py +2 -2
- omlish/check.py +202 -24
- omlish/collections/__init__.py +2 -2
- omlish/collections/utils.py +3 -3
- omlish/concurrent/threadlets.py +5 -0
- omlish/dataclasses/__init__.py +1 -0
- omlish/dataclasses/impl/init.py +10 -2
- omlish/dataclasses/impl/metadata.py +3 -3
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +16 -3
- omlish/diag/pycharm.py +69 -10
- omlish/docker.py +16 -11
- omlish/genmachine.py +2 -1
- omlish/graphs/trees.py +1 -1
- omlish/lang/__init__.py +13 -1
- omlish/lang/cached.py +5 -2
- omlish/lang/descriptors.py +33 -16
- omlish/lang/resources.py +60 -0
- omlish/lite/logs.py +133 -4
- omlish/logs/__init__.py +17 -2
- omlish/logs/configs.py +13 -1
- omlish/logs/formatters.py +0 -1
- omlish/marshal/__init__.py +2 -0
- omlish/marshal/helpers.py +27 -0
- omlish/specs/jsonschema/keywords/base.py +2 -2
- omlish/specs/jsonschema/keywords/parse.py +1 -1
- omlish/stats.py +1 -0
- {omlish-0.0.0.dev23.dist-info → omlish-0.0.0.dev24.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev23.dist-info → omlish-0.0.0.dev24.dist-info}/RECORD +33 -32
- {omlish-0.0.0.dev23.dist-info → omlish-0.0.0.dev24.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev23.dist-info → omlish-0.0.0.dev24.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev23.dist-info → omlish-0.0.0.dev24.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/bootstrap/diag.py
CHANGED
@@ -166,12 +166,12 @@ class PycharmBootstrap(SimpleBootstrap['PycharmBootstrap.Config']):
|
|
166
166
|
class Config(Bootstrap.Config):
|
167
167
|
debug_host: ta.Optional[str] = None
|
168
168
|
debug_port: ta.Optional[int] = None
|
169
|
-
|
169
|
+
version: ta.Optional[str] = None
|
170
170
|
|
171
171
|
def run(self) -> None:
|
172
172
|
if self._config.debug_port is not None:
|
173
173
|
diagpc.pycharm_remote_debugger_attach(
|
174
174
|
self._config.debug_host,
|
175
175
|
self._config.debug_port,
|
176
|
-
version=self._config.
|
176
|
+
version=self._config.version,
|
177
177
|
)
|
omlish/check.py
CHANGED
@@ -75,6 +75,9 @@ def enable_args_rendering() -> bool:
|
|
75
75
|
return True
|
76
76
|
|
77
77
|
|
78
|
+
enable_args_rendering()
|
79
|
+
|
80
|
+
|
78
81
|
#
|
79
82
|
|
80
83
|
|
@@ -142,7 +145,14 @@ def _unpack_isinstance_spec(spec: ta.Any) -> tuple:
|
|
142
145
|
|
143
146
|
def isinstance(v: ta.Any, spec: type[T] | tuple, msg: Message = None) -> T: # noqa
|
144
147
|
if not _isinstance(v, _unpack_isinstance_spec(spec)):
|
145
|
-
_raise(
|
148
|
+
_raise(
|
149
|
+
TypeError,
|
150
|
+
'Must be instance',
|
151
|
+
msg,
|
152
|
+
_Args(v, spec),
|
153
|
+
render_fmt='not isinstance(%s, %s)',
|
154
|
+
)
|
155
|
+
|
146
156
|
return v
|
147
157
|
|
148
158
|
|
@@ -155,7 +165,13 @@ def of_isinstance(spec: type[T] | tuple, msg: Message = None) -> ta.Callable[[ta
|
|
155
165
|
|
156
166
|
def cast(v: ta.Any, cls: type[T], msg: Message = None) -> T: # noqa
|
157
167
|
if not _isinstance(v, cls):
|
158
|
-
_raise(
|
168
|
+
_raise(
|
169
|
+
TypeError,
|
170
|
+
'Must be instance',
|
171
|
+
msg,
|
172
|
+
_Args(v, cls),
|
173
|
+
)
|
174
|
+
|
159
175
|
return v
|
160
176
|
|
161
177
|
|
@@ -168,7 +184,14 @@ def of_cast(cls: type[T], msg: Message = None) -> ta.Callable[[T], T]:
|
|
168
184
|
|
169
185
|
def not_isinstance(v: T, spec: ta.Any, msg: Message = None) -> T: # noqa
|
170
186
|
if _isinstance(v, _unpack_isinstance_spec(spec)):
|
171
|
-
_raise(
|
187
|
+
_raise(
|
188
|
+
TypeError,
|
189
|
+
'Must not be instance',
|
190
|
+
msg,
|
191
|
+
_Args(v, spec),
|
192
|
+
render_fmt='isinstance(%s, %s)',
|
193
|
+
)
|
194
|
+
|
172
195
|
return v
|
173
196
|
|
174
197
|
|
@@ -184,13 +207,27 @@ def of_not_isinstance(spec: ta.Any, msg: Message = None) -> ta.Callable[[T], T]:
|
|
184
207
|
|
185
208
|
def issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
|
186
209
|
if not _issubclass(v, spec):
|
187
|
-
_raise(
|
210
|
+
_raise(
|
211
|
+
TypeError,
|
212
|
+
'Must be subclass',
|
213
|
+
msg,
|
214
|
+
_Args(v, spec),
|
215
|
+
render_fmt='not issubclass(%s, %s)',
|
216
|
+
)
|
217
|
+
|
188
218
|
return v
|
189
219
|
|
190
220
|
|
191
221
|
def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
|
192
222
|
if _issubclass(v, spec):
|
193
|
-
_raise(
|
223
|
+
_raise(
|
224
|
+
TypeError,
|
225
|
+
'Must not be subclass',
|
226
|
+
msg,
|
227
|
+
_Args(v, spec),
|
228
|
+
render_fmt='issubclass(%s, %s)',
|
229
|
+
)
|
230
|
+
|
194
231
|
return v
|
195
232
|
|
196
233
|
|
@@ -199,19 +236,40 @@ def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: #
|
|
199
236
|
|
200
237
|
def in_(v: T, c: ta.Container[T], msg: Message = None) -> T:
|
201
238
|
if v not in c:
|
202
|
-
_raise(
|
239
|
+
_raise(
|
240
|
+
ValueError,
|
241
|
+
'Must be in',
|
242
|
+
msg,
|
243
|
+
_Args(v, c),
|
244
|
+
render_fmt='%s not in %s',
|
245
|
+
)
|
246
|
+
|
203
247
|
return v
|
204
248
|
|
205
249
|
|
206
250
|
def not_in(v: T, c: ta.Container[T], msg: Message = None) -> T:
|
207
251
|
if v in c:
|
208
|
-
_raise(
|
252
|
+
_raise(
|
253
|
+
ValueError,
|
254
|
+
'Must not be in',
|
255
|
+
msg,
|
256
|
+
_Args(v, c),
|
257
|
+
render_fmt='%s in %s',
|
258
|
+
)
|
259
|
+
|
209
260
|
return v
|
210
261
|
|
211
262
|
|
212
263
|
def empty(v: SizedT, msg: Message = None) -> SizedT:
|
213
264
|
if len(v) != 0:
|
214
|
-
_raise(
|
265
|
+
_raise(
|
266
|
+
ValueError,
|
267
|
+
'Must be empty',
|
268
|
+
msg,
|
269
|
+
_Args(v),
|
270
|
+
render_fmt='%s',
|
271
|
+
)
|
272
|
+
|
215
273
|
return v
|
216
274
|
|
217
275
|
|
@@ -222,20 +280,40 @@ def iterempty(v: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
|
|
222
280
|
except StopIteration:
|
223
281
|
pass
|
224
282
|
else:
|
225
|
-
_raise(
|
283
|
+
_raise(
|
284
|
+
ValueError,
|
285
|
+
'Must be empty',
|
286
|
+
msg,
|
287
|
+
_Args(v),
|
288
|
+
render_fmt='%s',
|
289
|
+
)
|
290
|
+
|
226
291
|
return v
|
227
292
|
|
228
293
|
|
229
294
|
def not_empty(v: SizedT, msg: Message = None) -> SizedT:
|
230
295
|
if len(v) == 0:
|
231
|
-
_raise(
|
296
|
+
_raise(
|
297
|
+
ValueError,
|
298
|
+
'Must not be empty',
|
299
|
+
msg,
|
300
|
+
_Args(v),
|
301
|
+
render_fmt='%s',
|
302
|
+
)
|
303
|
+
|
232
304
|
return v
|
233
305
|
|
234
306
|
|
235
307
|
def unique(it: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
|
236
308
|
dupes = [e for e, c in collections.Counter(it).items() if c > 1]
|
237
309
|
if dupes:
|
238
|
-
_raise(
|
310
|
+
_raise(
|
311
|
+
ValueError,
|
312
|
+
'Must be unique',
|
313
|
+
msg,
|
314
|
+
_Args(it, dupes),
|
315
|
+
)
|
316
|
+
|
239
317
|
return it
|
240
318
|
|
241
319
|
|
@@ -243,9 +321,15 @@ def single(obj: ta.Iterable[T], message: Message = None) -> T:
|
|
243
321
|
try:
|
244
322
|
[value] = obj
|
245
323
|
except ValueError:
|
246
|
-
_raise(
|
247
|
-
|
248
|
-
|
324
|
+
_raise(
|
325
|
+
ValueError,
|
326
|
+
'Must be single',
|
327
|
+
message,
|
328
|
+
_Args(obj),
|
329
|
+
render_fmt='%s',
|
330
|
+
)
|
331
|
+
|
332
|
+
return value
|
249
333
|
|
250
334
|
|
251
335
|
def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
|
@@ -254,11 +338,19 @@ def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
|
|
254
338
|
value = next(it)
|
255
339
|
except StopIteration:
|
256
340
|
return None
|
341
|
+
|
257
342
|
try:
|
258
343
|
next(it)
|
259
344
|
except StopIteration:
|
260
345
|
return value # noqa
|
261
|
-
|
346
|
+
|
347
|
+
_raise(
|
348
|
+
ValueError,
|
349
|
+
'Must be empty or single',
|
350
|
+
message,
|
351
|
+
_Args(obj),
|
352
|
+
render_fmt='%s',
|
353
|
+
)
|
262
354
|
|
263
355
|
|
264
356
|
##
|
@@ -266,12 +358,25 @@ def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
|
|
266
358
|
|
267
359
|
def none(v: ta.Any, msg: Message = None) -> None:
|
268
360
|
if v is not None:
|
269
|
-
_raise(
|
361
|
+
_raise(
|
362
|
+
ValueError,
|
363
|
+
'Must be None',
|
364
|
+
msg,
|
365
|
+
_Args(v),
|
366
|
+
render_fmt='%s',
|
367
|
+
)
|
270
368
|
|
271
369
|
|
272
370
|
def not_none(v: T | None, msg: Message = None) -> T:
|
273
371
|
if v is None:
|
274
|
-
_raise(
|
372
|
+
_raise(
|
373
|
+
ValueError,
|
374
|
+
'Must not be None',
|
375
|
+
msg,
|
376
|
+
_Args(v),
|
377
|
+
render_fmt='%s',
|
378
|
+
)
|
379
|
+
|
275
380
|
return v
|
276
381
|
|
277
382
|
|
@@ -280,42 +385,115 @@ def not_none(v: T | None, msg: Message = None) -> T:
|
|
280
385
|
|
281
386
|
def equal(v: T, o: ta.Any, msg: Message = None) -> T:
|
282
387
|
if o != v:
|
283
|
-
_raise(
|
388
|
+
_raise(
|
389
|
+
ValueError,
|
390
|
+
'Must be equal',
|
391
|
+
msg,
|
392
|
+
_Args(v, o),
|
393
|
+
render_fmt='%s != %s',
|
394
|
+
)
|
395
|
+
|
284
396
|
return v
|
285
397
|
|
286
398
|
|
287
399
|
def is_(v: T, o: ta.Any, msg: Message = None) -> T:
|
288
400
|
if o is not v:
|
289
|
-
_raise(
|
401
|
+
_raise(
|
402
|
+
ValueError,
|
403
|
+
'Must be the same',
|
404
|
+
msg,
|
405
|
+
_Args(v, o),
|
406
|
+
render_fmt='%s is not %s',
|
407
|
+
)
|
408
|
+
|
290
409
|
return v
|
291
410
|
|
292
411
|
|
293
412
|
def is_not(v: T, o: ta.Any, msg: Message = None) -> T:
|
294
413
|
if o is v:
|
295
|
-
_raise(
|
414
|
+
_raise(
|
415
|
+
ValueError,
|
416
|
+
'Must not be the same',
|
417
|
+
msg,
|
418
|
+
_Args(v, o),
|
419
|
+
render_fmt='%s is %s',
|
420
|
+
)
|
421
|
+
|
296
422
|
return v
|
297
423
|
|
298
424
|
|
299
425
|
def callable(v: T, msg: Message = None) -> T: # noqa
|
300
426
|
if not _callable(v):
|
301
|
-
_raise(
|
427
|
+
_raise(
|
428
|
+
TypeError,
|
429
|
+
'Must be callable',
|
430
|
+
msg,
|
431
|
+
_Args(v),
|
432
|
+
render_fmt='%s',
|
433
|
+
)
|
434
|
+
|
302
435
|
return v # type: ignore
|
303
436
|
|
304
437
|
|
305
438
|
def non_empty_str(v: str | None, msg: Message = None) -> str:
|
306
439
|
if not _isinstance(v, str) or not v:
|
307
|
-
_raise(
|
440
|
+
_raise(
|
441
|
+
ValueError,
|
442
|
+
'Must be non-empty str',
|
443
|
+
msg,
|
444
|
+
_Args(v),
|
445
|
+
render_fmt='%s',
|
446
|
+
)
|
447
|
+
|
308
448
|
return v
|
309
449
|
|
310
450
|
|
451
|
+
def replacing(expected: ta.Any, old: ta.Any, new: T, msg: Message = None) -> T:
|
452
|
+
if old != expected:
|
453
|
+
_raise(
|
454
|
+
ValueError,
|
455
|
+
'Must be replacing',
|
456
|
+
msg,
|
457
|
+
_Args(expected, old, new),
|
458
|
+
render_fmt='%s -> %s -> %s',
|
459
|
+
)
|
460
|
+
|
461
|
+
return new
|
462
|
+
|
463
|
+
|
464
|
+
def replacing_none(old: ta.Any, new: T, msg: Message = None) -> T:
|
465
|
+
if old is not None:
|
466
|
+
_raise(
|
467
|
+
ValueError,
|
468
|
+
'Must be replacing None',
|
469
|
+
msg,
|
470
|
+
_Args(old, new),
|
471
|
+
render_fmt='%s -> %s',
|
472
|
+
)
|
473
|
+
|
474
|
+
return new
|
475
|
+
|
476
|
+
|
311
477
|
##
|
312
478
|
|
313
479
|
|
314
480
|
def arg(v: bool, msg: Message = None) -> None:
|
315
481
|
if not v:
|
316
|
-
_raise(
|
482
|
+
_raise(
|
483
|
+
RuntimeError,
|
484
|
+
'Argument condition not met',
|
485
|
+
msg,
|
486
|
+
_Args(v),
|
487
|
+
render_fmt='%s',
|
488
|
+
)
|
317
489
|
|
318
490
|
|
319
491
|
def state(v: bool, msg: Message = None) -> None:
|
320
492
|
if not v:
|
321
|
-
_raise(
|
493
|
+
_raise(
|
494
|
+
RuntimeError,
|
495
|
+
'State condition not met',
|
496
|
+
msg,
|
497
|
+
_Args(v),
|
498
|
+
render_fmt='%s',
|
499
|
+
)
|
omlish/collections/__init__.py
CHANGED
omlish/collections/utils.py
CHANGED
@@ -72,7 +72,7 @@ def unique(
|
|
72
72
|
return ret
|
73
73
|
|
74
74
|
|
75
|
-
def
|
75
|
+
def make_map(
|
76
76
|
kvs: ta.Iterable[tuple[K, V]],
|
77
77
|
*,
|
78
78
|
identity: bool = False,
|
@@ -88,14 +88,14 @@ def unique_map(
|
|
88
88
|
return d
|
89
89
|
|
90
90
|
|
91
|
-
def
|
91
|
+
def make_map_by(
|
92
92
|
fn: ta.Callable[[V], K],
|
93
93
|
vs: ta.Iterable[V],
|
94
94
|
*,
|
95
95
|
identity: bool = False,
|
96
96
|
strict: bool = False,
|
97
97
|
) -> ta.MutableMapping[K, V]:
|
98
|
-
return
|
98
|
+
return make_map(
|
99
99
|
((fn(v), v) for v in vs),
|
100
100
|
identity=identity,
|
101
101
|
strict=strict,
|
omlish/concurrent/threadlets.py
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
"""
|
2
|
+
An abstraction over greenlet's api. Greenlet doesn't currently support nogil but its functionality is needed for async
|
3
|
+
bridge code (both here and in sqlalchemy). This can be implemented with real threads at the expense of overhead, but
|
4
|
+
this code is only intended to be used in already fairly heavy situations (bootstrap, db calls).
|
5
|
+
"""
|
1
6
|
import abc
|
2
7
|
import dataclasses as dc
|
3
8
|
import typing as ta
|
omlish/dataclasses/__init__.py
CHANGED
omlish/dataclasses/impl/init.py
CHANGED
@@ -130,9 +130,17 @@ class InitBuilder:
|
|
130
130
|
cas = ', '.join(p.name for p in csig.parameters.values())
|
131
131
|
body_lines.append(f'if not {cn}({cas}): raise __dataclass_CheckError__')
|
132
132
|
|
133
|
-
|
133
|
+
inits = self._info.merged_metadata.get(Init, [])
|
134
|
+
mro_dct = lang.build_mro_dict(self._info.cls)
|
135
|
+
mro_v_ids = set(map(id, mro_dct.values()))
|
136
|
+
props_by_fget_id = {id(v.fget): v for v in mro_dct.values() if isinstance(v, property) and v.fget is not None}
|
137
|
+
for i, obj in enumerate(inits):
|
138
|
+
if (obj_id := id(obj)) not in mro_v_ids and obj_id in props_by_fget_id:
|
139
|
+
obj = props_by_fget_id[obj_id].__get__
|
140
|
+
elif isinstance(obj, property):
|
141
|
+
obj = obj.__get__
|
134
142
|
cn = f'__dataclass_init_{i}__'
|
135
|
-
locals[cn] =
|
143
|
+
locals[cn] = obj
|
136
144
|
body_lines.append(f'{cn}({self._self_name})')
|
137
145
|
|
138
146
|
if not body_lines:
|
@@ -149,7 +149,7 @@ class ClassInfo:
|
|
149
149
|
|
150
150
|
@cached.property
|
151
151
|
def generic_mro_lookup(self) -> ta.Mapping[type, rfl.Type]:
|
152
|
-
return col.
|
152
|
+
return col.make_map(((check.not_none(rfl.get_concrete_type(g)), g) for g in self.generic_mro), strict=True)
|
153
153
|
|
154
154
|
@cached.property
|
155
155
|
def generic_replaced_field_types(self) -> ta.Mapping[str, rfl.Type]:
|
omlish/dataclasses/utils.py
CHANGED
@@ -4,6 +4,8 @@ import types
|
|
4
4
|
import typing as ta
|
5
5
|
|
6
6
|
from .. import check
|
7
|
+
from .impl.metadata import METADATA_ATTR
|
8
|
+
from .impl.metadata import UserMetadata
|
7
9
|
from .impl.params import DEFAULT_FIELD_EXTRAS
|
8
10
|
from .impl.params import FieldExtras
|
9
11
|
from .impl.params import get_field_extras
|
@@ -50,6 +52,13 @@ def chain_metadata(*mds: ta.Mapping) -> types.MappingProxyType:
|
|
50
52
|
return types.MappingProxyType(collections.ChainMap(*mds)) # type: ignore # noqa
|
51
53
|
|
52
54
|
|
55
|
+
def update_class_metadata(cls: type[T], *args: ta.Any) -> type[T]:
|
56
|
+
check.isinstance(cls, type)
|
57
|
+
setattr(cls, METADATA_ATTR, md := getattr(cls, METADATA_ATTR, {}))
|
58
|
+
md.setdefault(UserMetadata, []).extend(args)
|
59
|
+
return cls
|
60
|
+
|
61
|
+
|
53
62
|
def update_field_metadata(f: dc.Field, nmd: ta.Mapping) -> dc.Field:
|
54
63
|
check.isinstance(f, dc.Field)
|
55
64
|
f.metadata = chain_metadata(nmd, f.metadata)
|
@@ -79,9 +88,13 @@ def update_fields(
|
|
79
88
|
|
80
89
|
else:
|
81
90
|
for a in fields:
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
try:
|
92
|
+
v = cls.__dict__[a]
|
93
|
+
except KeyError:
|
94
|
+
v = dc.field()
|
95
|
+
else:
|
96
|
+
if not isinstance(v, dc.Field):
|
97
|
+
v = dc.field(default=v)
|
85
98
|
setattr(cls, a, fn(a, v))
|
86
99
|
|
87
100
|
return cls
|
omlish/diag/pycharm.py
CHANGED
@@ -17,6 +17,16 @@ else:
|
|
17
17
|
##
|
18
18
|
|
19
19
|
|
20
|
+
PYCHARM_HOSTED_ENV_VAR = 'PYCHARM_HOSTED'
|
21
|
+
|
22
|
+
|
23
|
+
def is_pycharm_hosted() -> bool:
|
24
|
+
return PYCHARM_HOSTED_ENV_VAR in os.environ
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
20
30
|
PYCHARM_HOME = '/Applications/PyCharm.app'
|
21
31
|
|
22
32
|
|
@@ -45,6 +55,53 @@ def get_pycharm_version() -> str | None:
|
|
45
55
|
##
|
46
56
|
|
47
57
|
|
58
|
+
def _import_pydevd_pycharm(*, version: str | None = None) -> ta.Any:
|
59
|
+
if (
|
60
|
+
'pydevd_pycharm' in sys.modules or
|
61
|
+
(version is None and lang.can_import('pydevd_pycharm'))
|
62
|
+
):
|
63
|
+
# Can't unload, nothing we can do
|
64
|
+
import pydevd_pycharm # noqa
|
65
|
+
return pydevd_pycharm
|
66
|
+
|
67
|
+
proc = subprocess.run([ # noqa
|
68
|
+
sys.executable,
|
69
|
+
'-m', 'pip',
|
70
|
+
'show',
|
71
|
+
'pydevd_pycharm',
|
72
|
+
], stdout=subprocess.PIPE)
|
73
|
+
|
74
|
+
if not proc.returncode:
|
75
|
+
info = {
|
76
|
+
k: v.strip()
|
77
|
+
for l in proc.stdout.decode().splitlines()
|
78
|
+
if (s := l.strip())
|
79
|
+
for k, _, v in [s.partition(':')]
|
80
|
+
}
|
81
|
+
|
82
|
+
installed_version = info['Version']
|
83
|
+
if installed_version == version:
|
84
|
+
import pydevd_pycharm # noqa
|
85
|
+
return pydevd_pycharm
|
86
|
+
|
87
|
+
subprocess.check_call([
|
88
|
+
sys.executable,
|
89
|
+
'-m', 'pip',
|
90
|
+
'uninstall', '-y',
|
91
|
+
'pydevd_pycharm',
|
92
|
+
])
|
93
|
+
|
94
|
+
subprocess.check_call([
|
95
|
+
sys.executable,
|
96
|
+
'-m', 'pip',
|
97
|
+
'install',
|
98
|
+
'pydevd_pycharm' + (f'=={version}' if version is not None else ''),
|
99
|
+
])
|
100
|
+
|
101
|
+
import pydevd_pycharm # noqa
|
102
|
+
return pydevd_pycharm
|
103
|
+
|
104
|
+
|
48
105
|
def pycharm_remote_debugger_attach(
|
49
106
|
host: str | None,
|
50
107
|
port: int,
|
@@ -56,25 +113,27 @@ def pycharm_remote_debugger_attach(
|
|
56
113
|
# check.non_empty_str(version)
|
57
114
|
|
58
115
|
if host is None:
|
59
|
-
if
|
116
|
+
if (
|
117
|
+
sys.platform == 'linux' and
|
118
|
+
docker.is_likely_in_docker() and
|
119
|
+
docker.get_docker_host_platform() == 'darwin'
|
120
|
+
):
|
60
121
|
host = docker.DOCKER_FOR_MAC_HOSTNAME
|
61
122
|
else:
|
62
123
|
host = 'localhost'
|
63
124
|
|
64
|
-
|
125
|
+
if ta.TYPE_CHECKING:
|
65
126
|
import pydevd_pycharm # noqa
|
66
|
-
|
67
|
-
|
68
|
-
sys.executable,
|
69
|
-
'-mpip',
|
70
|
-
'install',
|
71
|
-
'pydevd-pycharm' + (f'~={version}' if version is not None else ''),
|
72
|
-
])
|
127
|
+
else:
|
128
|
+
pydevd_pycharm = _import_pydevd_pycharm(version=version)
|
73
129
|
|
74
|
-
import pydevd_pycharm # noqa
|
75
130
|
pydevd_pycharm.settrace(
|
76
131
|
host,
|
77
132
|
port=port,
|
78
133
|
stdoutToServer=True,
|
79
134
|
stderrToServer=True,
|
80
135
|
)
|
136
|
+
|
137
|
+
|
138
|
+
if __name__ == '__main__':
|
139
|
+
print(get_pycharm_version())
|
omlish/docker.py
CHANGED
@@ -15,6 +15,7 @@ apil="application/vnd.docker.distribution.manifest.list.v2+json"
|
|
15
15
|
curl -H "Accept: ${api}" -H "Accept: ${apil}" -H "Authorization: Bearer $token" -s "https://registry-1.docker.io/v2/${repo}/manifests/latest" | jq .
|
16
16
|
""" # noqa
|
17
17
|
import datetime
|
18
|
+
import os
|
18
19
|
import re
|
19
20
|
import shlex
|
20
21
|
import subprocess
|
@@ -38,15 +39,12 @@ else:
|
|
38
39
|
|
39
40
|
|
40
41
|
@dc.dataclass(frozen=True)
|
42
|
+
@msh.update_object_metadata(field_naming=msh.Naming.CAMEL, unknown_field='x')
|
43
|
+
@msh.update_fields_metadata(['id'], name='ID')
|
41
44
|
class PsItem(lang.Final):
|
42
|
-
dc.metadata(msh.ObjectMetadata(
|
43
|
-
field_naming=msh.Naming.CAMEL,
|
44
|
-
unknown_field='x',
|
45
|
-
))
|
46
|
-
|
47
45
|
command: str
|
48
46
|
created_at: datetime.datetime
|
49
|
-
id: str
|
47
|
+
id: str
|
50
48
|
image: str
|
51
49
|
labels: str
|
52
50
|
local_volumes: str
|
@@ -101,12 +99,8 @@ def cli_ps() -> list[PsItem]:
|
|
101
99
|
|
102
100
|
|
103
101
|
@dc.dataclass(frozen=True)
|
102
|
+
@msh.update_object_metadata(field_naming=msh.Naming.CAMEL, unknown_field='x')
|
104
103
|
class Inspect(lang.Final):
|
105
|
-
dc.metadata(msh.ObjectMetadata(
|
106
|
-
field_naming=msh.Naming.CAMEL,
|
107
|
-
unknown_field='x',
|
108
|
-
))
|
109
|
-
|
110
104
|
id: str
|
111
105
|
created: datetime.datetime
|
112
106
|
|
@@ -186,3 +180,14 @@ def is_likely_in_docker() -> bool:
|
|
186
180
|
with open('/proc/mounts') as f: # type: ignore
|
187
181
|
ls = f.readlines()
|
188
182
|
return any(_LIKELY_IN_DOCKER_PATTERN.match(l) for l in ls)
|
183
|
+
|
184
|
+
|
185
|
+
##
|
186
|
+
|
187
|
+
|
188
|
+
# Set by pyproject, docker-dev script
|
189
|
+
DOCKER_HOST_PLATFORM_KEY = 'DOCKER_HOST_PLATFORM'
|
190
|
+
|
191
|
+
|
192
|
+
def get_docker_host_platform() -> str | None:
|
193
|
+
return os.environ.get(DOCKER_HOST_PLATFORM_KEY)
|