istr-python 1.1.3__tar.gz → 1.1.4.post0__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: istr-python
3
- Version: 1.1.3
3
+ Version: 1.1.4.post0
4
4
  Summary: istr - strings you can count on
5
5
  Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/salabim/istr
@@ -558,8 +558,30 @@ istr.digits('3-') ==> istr('34567879')
558
558
  istr.digits('X-') ==> istr('XYZ')
559
559
  ```
560
560
 
561
+ #### Decomposing to and composing from letter variables
562
+
563
+ When we have an istr, we can decompose the value into individual one letter (global) variables with the `decompose()` method.
564
+ E.g.
565
+
566
+ ```
567
+ istr(485).decompose("abc")
568
+ ```
569
+ will set the global variables `a`, `b` and `c` to be set to `istr(4)`. `istr(8)` and` istr(5)`.
570
+ Note that the length of the letters specifier must be the same as the length of the istr. Furthermore, multiple values for the same variables result in a ValueError.
571
+
572
+ With `istr.compose()`, an istr can be constructed from individual (global) variables.
573
+ E.g.
574
+
575
+ ```
576
+ x = 3
577
+ y = 9
578
+ z = 6
579
+ test = istr.compose("xyz")
580
+ ```
581
+ Now, `test` will be `istr(396)` .
561
582
 
562
583
  #### Subclassing istr
584
+
563
585
  When a class is derived from istr, all methods will return that newly derived class.
564
586
 
565
587
  E.g.
@@ -545,8 +545,30 @@ istr.digits('3-') ==> istr('34567879')
545
545
  istr.digits('X-') ==> istr('XYZ')
546
546
  ```
547
547
 
548
+ #### Decomposing to and composing from letter variables
549
+
550
+ When we have an istr, we can decompose the value into individual one letter (global) variables with the `decompose()` method.
551
+ E.g.
552
+
553
+ ```
554
+ istr(485).decompose("abc")
555
+ ```
556
+ will set the global variables `a`, `b` and `c` to be set to `istr(4)`. `istr(8)` and` istr(5)`.
557
+ Note that the length of the letters specifier must be the same as the length of the istr. Furthermore, multiple values for the same variables result in a ValueError.
558
+
559
+ With `istr.compose()`, an istr can be constructed from individual (global) variables.
560
+ E.g.
561
+
562
+ ```
563
+ x = 3
564
+ y = 9
565
+ z = 6
566
+ test = istr.compose("xyz")
567
+ ```
568
+ Now, `test` will be `istr(396)` .
548
569
 
549
570
  #### Subclassing istr
571
+
550
572
  When a class is derived from istr, all methods will return that newly derived class.
551
573
 
552
574
  E.g.
@@ -5,12 +5,13 @@
5
5
  # |_||___/ \__||_|
6
6
  # strings you can count on
7
7
 
8
- __version__ = "1.1.3"
8
+ __version__ = "1.1.4"
9
9
  import functools
10
10
  import math
11
11
  import itertools
12
12
  import types
13
13
  import sys
14
+ import inspect
14
15
 
15
16
  """
16
17
  Note: the changelog is now in changelog.md
@@ -305,7 +306,7 @@ class istr(str):
305
306
  # like repr, but if obj is an istr, the as_repr is not used to make sure the
306
307
  # the returned value is istr(...) and not infuenced by the repr mode
307
308
  if isinstance(obj, self.__class__):
308
- return f"{obj.__class__.__name__}({super(istr,obj).__repr__()})"
309
+ return f"{obj.__class__.__name__}({super(istr, obj).__repr__()})"
309
310
  return repr(obj)
310
311
 
311
312
  def _int_method(self, name, op, *args):
@@ -365,14 +366,28 @@ class istr(str):
365
366
  return n % 2 == 1
366
367
 
367
368
  def is_square(self):
369
+ return istr._is_power_of(self, 2)
370
+
371
+ def is_cube(self):
372
+ return istr._is_power_of(self, 3)
373
+
374
+ def is_power_of(self, power_of):
375
+ return istr._is_power_of(self, power_of)
376
+
377
+ @staticmethod
378
+ def _is_power_of(self, power_of):
368
379
  if isinstance(self, istr):
369
380
  if not self.is_int():
370
381
  raise TypeError(f"not interpretable as int: {self._frepr(self)}")
371
382
  n = self._as_int
372
383
  else:
373
384
  n = int(self)
385
+ if power_of < 1:
386
+ raise ValueError(f"power_of must be >=1; not {power_of}")
387
+ if not isinstance(power_of, int):
388
+ raise TypeError(f"power_of must be int; not {type(power_of)}")
374
389
 
375
- return n >= 0 and self == math.isqrt(n) ** 2
390
+ return n >= 0 and self == round(n ** (1 / power_of)) ** power_of
376
391
 
377
392
  def is_prime(self):
378
393
  if isinstance(self, istr):
@@ -394,6 +409,27 @@ class istr(str):
394
409
  return False
395
410
  return True
396
411
 
412
+ def decompose(self, letters, namespace=None):
413
+ """
414
+ decompose letter variables into local variables
415
+ each letter variable must represent just one character
416
+ same letter variables represent the the same character
417
+ the istr must have the same length as the letters
418
+ """
419
+ if namespace is None:
420
+ namespace = inspect.currentframe().f_back.f_globals
421
+ lookup = {}
422
+
423
+ for letter, ch in zip(letters, self):
424
+ if letter in lookup and lookup[letter] != ch:
425
+ raise ValueError(f"multiple values found for variable {letter}")
426
+ if not letter.isidentifier():
427
+ raise ValueError(f"{letter} cannot be used as a variable")
428
+ lookup[letter] = ch
429
+ if len(letters) != len(self):
430
+ raise ValueError(f"incorrect number of variables {len(letters)}; should be {len(self)}")
431
+ namespace.update(lookup)
432
+
397
433
  def __or__(self, other):
398
434
  try:
399
435
  return self.__class__(str(self).__add__(other))
@@ -483,8 +519,7 @@ class istr(str):
483
519
 
484
520
  cls._int_format = int_format
485
521
 
486
- def __enter__(self):
487
- ...
522
+ def __enter__(self): ...
488
523
 
489
524
  def __exit__(self, exc_type, exc_value, exc_tb):
490
525
  self.saved_cls._int_format = self.saved_int_format
@@ -494,6 +529,8 @@ class istr(str):
494
529
  def __new__(cls, cls_repr_mode, mode=None):
495
530
  if mode is None:
496
531
  return cls_repr_mode._repr_mode
532
+ if mode == int:
533
+ mode = "int"
497
534
  if mode in ("istr", "str", "int"): # _istr is used only for TypeErrors
498
535
  return super().__new__(cls)
499
536
  raise TypeError(f"mode not 'istr', 'str' or 'int', but {repr(mode)}")
@@ -503,8 +540,7 @@ class istr(str):
503
540
  self.saved_cls = cls
504
541
  cls._repr_mode = mode
505
542
 
506
- def __enter__(self):
507
- ...
543
+ def __enter__(self): ...
508
544
 
509
545
  def __exit__(self, exc_type, exc_value, exc_tb):
510
546
  self.saved_cls._repr_mode = self.saved_repr_mode
@@ -523,8 +559,7 @@ class istr(str):
523
559
  self.saved_cls = cls
524
560
  cls._base = base
525
561
 
526
- def __enter__(self):
527
- ...
562
+ def __enter__(self): ...
528
563
 
529
564
  def __exit__(self, exc_type, exc_value, exc_tb):
530
565
  self.saved_cls._base = self.saved_base
@@ -601,23 +636,39 @@ class istr(str):
601
636
  result = istr("".join(result))
602
637
  cls._digits_cache[key] = result
603
638
  return result
604
-
605
- istr.type=type(istr(0))
639
+
640
+ @classmethod
641
+ def compose(cls, letters, namespace=None):
642
+ """
643
+ compose an istr from individual letter variables
644
+ """
645
+ if namespace is None:
646
+ namespace = inspect.currentframe().f_back.f_globals
647
+ for letter in letters:
648
+ if letter not in namespace:
649
+ raise ValueError(f"variable {letter} not defined")
650
+
651
+ return istr("").join(istr(namespace[letter]) for letter in letters)
652
+
653
+
654
+ istr.type = type(istr(0))
606
655
 
607
656
 
608
- def main():
609
- ...
657
+ def main(): ...
658
+
610
659
 
611
660
  class istrModule(types.ModuleType):
612
661
  def __call__(self, *args, **kwargs):
613
662
  return istr.__call__(*args, **kwargs)
663
+
614
664
  def __setattr__(self, item, value):
615
- setattr(istr,item,value)
616
- def __getattr__(self, item,):
617
- return getattr(istr,item)
665
+ setattr(istr, item, value)
666
+
667
+ def __getattr__(self, item):
668
+ return getattr(istr, item)
618
669
 
619
- sys.modules["istr"].__class__ = istrModule
620
670
 
621
671
  if __name__ == "__main__":
622
672
  main()
623
-
673
+ else:
674
+ sys.modules["istr"].__class__ = istrModule
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: istr-python
3
- Version: 1.1.3
3
+ Version: 1.1.4.post0
4
4
  Summary: istr - strings you can count on
5
5
  Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/salabim/istr
@@ -558,8 +558,30 @@ istr.digits('3-') ==> istr('34567879')
558
558
  istr.digits('X-') ==> istr('XYZ')
559
559
  ```
560
560
 
561
+ #### Decomposing to and composing from letter variables
562
+
563
+ When we have an istr, we can decompose the value into individual one letter (global) variables with the `decompose()` method.
564
+ E.g.
565
+
566
+ ```
567
+ istr(485).decompose("abc")
568
+ ```
569
+ will set the global variables `a`, `b` and `c` to be set to `istr(4)`. `istr(8)` and` istr(5)`.
570
+ Note that the length of the letters specifier must be the same as the length of the istr. Furthermore, multiple values for the same variables result in a ValueError.
571
+
572
+ With `istr.compose()`, an istr can be constructed from individual (global) variables.
573
+ E.g.
574
+
575
+ ```
576
+ x = 3
577
+ y = 9
578
+ z = 6
579
+ test = istr.compose("xyz")
580
+ ```
581
+ Now, `test` will be `istr(396)` .
561
582
 
562
583
  #### Subclassing istr
584
+
563
585
  When a class is derived from istr, all methods will return that newly derived class.
564
586
 
565
587
  E.g.
@@ -10,7 +10,7 @@ authors = [
10
10
  { name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com" },
11
11
  ]
12
12
  description = "istr - strings you can count on"
13
- version = "1.1.3"
13
+ version = "1.1.4.post0"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.7"
16
16
  dependencies = []
@@ -5,11 +5,11 @@ import sys
5
5
  import re
6
6
  from pathlib import Path
7
7
 
8
- # if __name__ == "__main__": # to make the tests run without the pytest cli
9
- # file_folder = os.path.dirname(__file__)
10
- # os.chdir(file_folder)
11
- # sys.path.insert(0, file_folder + "/../istr")
12
-
8
+ if __name__ == "__main__": # to make the tests run without the pytest cli
9
+ file_folder = os.path.dirname(__file__)
10
+ os.chdir(file_folder)
11
+ sys.path.insert(0, file_folder + "/../istr")
12
+
13
13
  import pytest
14
14
 
15
15
  import istr
@@ -330,7 +330,43 @@ def test_is_square():
330
330
  assert istr.is_square(4)
331
331
  assert istr.is_square(16)
332
332
 
333
-
333
+ def test_is_cube():
334
+ assert not istr(-1).is_cube()
335
+ assert istr(0).is_cube()
336
+ assert istr(1).is_cube()
337
+ assert not istr(2).is_cube()
338
+ assert istr(8).is_cube()
339
+ assert istr(27).is_cube()
340
+ assert not istr(99).is_cube()
341
+ with pytest.raises(TypeError, match=re.escape(f"not interpretable as int")):
342
+ istr("a").is_cube()
343
+ assert istr.is_cube(0)
344
+ assert istr.is_cube(1)
345
+ assert not istr.is_cube(2)
346
+ assert istr.is_cube(8)
347
+ assert istr.is_cube(27)
348
+
349
+
350
+ def test_is_power_of():
351
+ assert not istr(-1).is_power_of(3)
352
+ assert istr(0).is_power_of(3)
353
+ assert istr(1).is_power_of(3)
354
+ assert not istr(2).is_power_of(3)
355
+ assert istr(8).is_power_of(3)
356
+ assert istr(27).is_power_of(3)
357
+ assert not istr(99).is_power_of(3)
358
+ with pytest.raises(TypeError, match=re.escape(f"not interpretable as int")):
359
+ istr("a").is_power_of(3)
360
+ assert istr.is_power_of(0,3)
361
+ assert istr.is_power_of(1,3)
362
+ assert not istr.is_power_of(2,3)
363
+ assert istr.is_power_of(8,3)
364
+ assert istr.is_power_of(27,3)
365
+ with pytest.raises(TypeError):
366
+ istr(1).is_power_of(3.1)
367
+ with pytest.raises(ValueError):
368
+ istr(1).is_power_of(-1)
369
+
334
370
  def test_is_prime():
335
371
  assert not istr(0).is_prime()
336
372
  assert not istr(1).is_prime()
@@ -677,12 +713,50 @@ def test_all_distinct():
677
713
 
678
714
 
679
715
  def test_subclassing():
680
- class jstr(istr.type):
681
- ...
716
+ class jstr(istr.type): ...
682
717
 
683
718
  assert jstr(5).equals(jstr(5))
684
719
  assert repr(jstr(*range(3))) == "(jstr('0'), jstr('1'), jstr('2'))"
685
720
 
686
721
 
722
+ def test_decompose():
723
+ istr("123").decompose("xyz")
724
+ assert x == 1
725
+ assert y == 2
726
+ assert z == 3
727
+ istr(456).decompose("xyz")
728
+ assert x == 4
729
+ assert y == 5
730
+ assert z == 6
731
+ istr(1231).decompose("xyzx")
732
+ assert x == 1
733
+ assert y == 2
734
+ assert z == 3
735
+ namespace = {}
736
+ istr(123).decompose("xyz", namespace=namespace)
737
+ assert namespace == dict(x=istr(1), y=istr(2), z=istr(3))
738
+
739
+ with pytest.raises(ValueError):
740
+ istr(1234).decompose("xyzx")
741
+ with pytest.raises(ValueError):
742
+ istr(1234).decompose("xyz")
743
+ with pytest.raises(ValueError):
744
+ istr(12).decompose("xyz")
745
+ with pytest.raises(ValueError):
746
+ istr(123).decompose("xy1")
747
+
748
+
749
+ def test_compose():
750
+ x = 1
751
+ y = 2
752
+ z = 3
753
+ s = istr.compose("xyz")
754
+ assert s == 123
755
+ with pytest.raises(ValueError):
756
+ s = istr.compose("wxyz", globals()) # w is not defined
757
+ s = istr.compose("xyz", namespace=dict(x=3, y=istr(4), z="5"))
758
+ assert s == 345
759
+
760
+
687
761
  if __name__ == "__main__":
688
762
  pytest.main(["-vv", "-s", "-x", __file__])
File without changes