Commit 1b109b723c9ab0ff32ddf3e837bab5063525e5b9
1 parent
b999fd50
Exists in
dev
New version of the code style page - added lot of new stuff
Showing
1 changed file
with
358 additions
and
5 deletions
Show diff stats
doc/code_style/my_package1/my_module1.py
... | ... | @@ -6,13 +6,22 @@ |
6 | 6 | |
7 | 7 | This file conforms to the Sphinx Napoleon syntax : |
8 | 8 | https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html |
9 | + | |
10 | + VERSION 17.02.2022 | |
11 | + | |
12 | + Changes : | |
13 | + | |
14 | + - added new type annotation : Literal | |
15 | + | |
16 | + - added new types : NamedTuple, Dataclass, Enum, TypedDict | |
17 | + | |
18 | + - added custom and derived Exceptions | |
9 | 19 | ================================================================= |
10 | 20 | """ |
11 | 21 | |
12 | 22 | # This import MUST BE at the very beginning of the file (or syntax error...) |
13 | 23 | from __future__ import annotations |
14 | 24 | |
15 | -VERSION = "16.02.2022" | |
16 | 25 | |
17 | 26 | # TODO : |
18 | 27 | # - exception (custom) |
... | ... | @@ -29,7 +38,9 @@ VERSION = "16.02.2022" |
29 | 38 | |
30 | 39 | # --- 1) GENERAL PURPOSE IMPORTS --- |
31 | 40 | |
32 | -from typing import Dict, List, Tuple, Sequence, Any, TypeVar, Union, Callable | |
41 | +from typing import Dict, List, Tuple, Sequence, Any, TypeVar, Union, Callable, Literal | |
42 | +import typing | |
43 | + | |
33 | 44 | import platform |
34 | 45 | from datetime import date |
35 | 46 | import random |
... | ... | @@ -66,13 +77,13 @@ IS_WINDOWS = platform.system() == "Windows" |
66 | 77 | # - Typehint : Union (but not yet '|', only available with python 3.10) |
67 | 78 | |
68 | 79 | def general_function_that_returns_a_float( |
69 | - arg_a: int, arg_b: Union[str, int, float], arg_c: float = 1.2, arg_d: bool = True | |
80 | + arg_a: int, arg_b: str | int, arg_c: float = 1.2, arg_d: bool = True | |
70 | 81 | ) -> float: |
71 | - """This function illustrates Typehint 'Union' (but not yet '|', only available with python 3.10) | |
82 | + """This function illustrates Typehint 'Union' (or '|') | |
72 | 83 | |
73 | 84 | Args: |
74 | 85 | arg_a: the path of the file to wrap |
75 | - arg_b: instance to wrap (in Python 3.10 we will be able to use 'str | int | float' instead) | |
86 | + arg_b: instance to wrap | |
76 | 87 | arg_c: toto |
77 | 88 | arg_d: whether or not to delete the file when the File instance is destructed |
78 | 89 | |
... | ... | @@ -83,6 +94,14 @@ def general_function_that_returns_a_float( |
83 | 94 | AttributeError: The ``Raises`` section is a list of all exceptions |
84 | 95 | that are relevant to the interface. |
85 | 96 | ValueError: If `arg_a` is equal to `arg_b`. |
97 | + | |
98 | + Usage: | |
99 | + | |
100 | + general_function_that_returns_a_float(arg_a=1, arg_b="toto") # => OK | |
101 | + | |
102 | + general_function_that_returns_a_float(arg_a=1, arg_b=1.2) # => KO (float not allowed) | |
103 | + | |
104 | + | |
86 | 105 | """ |
87 | 106 | |
88 | 107 | # comment on a |
... | ... | @@ -94,6 +113,7 @@ def general_function_that_returns_a_float( |
94 | 113 | return 3.5 |
95 | 114 | |
96 | 115 | |
116 | + | |
97 | 117 | # - Typehint : Tuple (immutable) |
98 | 118 | |
99 | 119 | def general_function_that_returns_a_tuple_of_3_elem( |
... | ... | @@ -260,6 +280,40 @@ def do_twice( |
260 | 280 | print(func(arg1, arg2, arg3)) |
261 | 281 | |
262 | 282 | |
283 | +# - Typehint : typing.Literal (new in 3.8) | |
284 | + | |
285 | +def validate_simple(data: Any) -> Literal[True]: | |
286 | + """This function illustrates Typehint 'Literal' (new in 3.8) | |
287 | + | |
288 | + => should always return True | |
289 | + """ | |
290 | + pass | |
291 | + | |
292 | + | |
293 | +MODE = Literal['r', 'rb', 'w', 'wb'] | |
294 | + | |
295 | + | |
296 | +def open_helper(file: str, mode: MODE) -> str: | |
297 | + """This function illustrates Typehint 'Literal' (new in 3.8) | |
298 | + | |
299 | + (Type alias :) | |
300 | + | |
301 | + MODE = Literal['r', 'rb', 'w', 'wb'] | |
302 | + | |
303 | + Usage: | |
304 | + | |
305 | + OK : | |
306 | + | |
307 | + >>> open_helper('/some/path', 'r') | |
308 | + | |
309 | + Error : | |
310 | + | |
311 | + >>> open_helper('/other/path', 'typo') | |
312 | + """ | |
313 | + | |
314 | + pass | |
315 | + | |
316 | + | |
263 | 317 | # ''' |
264 | 318 | # ******************************* |
265 | 319 | # CUSTOM EXCEPTIONS CLASSES |
... | ... | @@ -299,6 +353,98 @@ class UnimplementedGenericCmdException(Exception): |
299 | 353 | return f"({type(self).__name__}): Device Generic command has no implementation in the controller" |
300 | 354 | |
301 | 355 | |
356 | +class MyImprovedException(Exception): | |
357 | + """Improved Exeption class with predefined list of standard error messages, and optional specific message | |
358 | + | |
359 | + Usage: | |
360 | + | |
361 | + >>> e = MyImprovedException(MyImprovedException.ERROR_BAD_PARAM) | |
362 | + >>> e.error_msg | |
363 | + 'Bad Parameter' | |
364 | + >>> e | |
365 | + MyImprovedException('Bad Parameter') | |
366 | + >>> print(e) | |
367 | + (MyImprovedException): Bad Parameter | |
368 | + | |
369 | + >>> e = MyImprovedException(MyImprovedException.ERROR_BAD_PARAM, "my specific message added") | |
370 | + >>> e | |
371 | + MyImprovedException('Bad Parameter', 'my specific message added') | |
372 | + >>> print(e) | |
373 | + (MyImprovedException): Bad Parameter ; Specific message: my specific message added | |
374 | + """ | |
375 | + | |
376 | + # List of standard error messages | |
377 | + ERROR_UNDEFINED_PARAM = "Parameter not defined" | |
378 | + ERROR_BAD_PARAM = "Bad Parameter" | |
379 | + ERROR_MISSING_PARAM = "a Parameter is missing" | |
380 | + ERROR_TOO_MANY_PARAM = "Too many Parameters" | |
381 | + | |
382 | + def __init__(self, error_msg: str, specific_msg: str = None): | |
383 | + #super().__init__(error_msg) | |
384 | + self.error_msg = error_msg | |
385 | + self.specific_msg = specific_msg | |
386 | + | |
387 | + def __str__(self): | |
388 | + #msg = f"({type(self).__name__}): {self.error_msg}" | |
389 | + msg = f"({self.__class__.__name__}): {self.error_msg}" | |
390 | + if self.specific_msg: | |
391 | + msg += f" ; Specific message: {self.specific_msg}" | |
392 | + return msg | |
393 | + | |
394 | + | |
395 | +class MyOwnDerivedException(MyImprovedException): | |
396 | + """ | |
397 | + Usage: | |
398 | + | |
399 | + >>> try: | |
400 | + ... print("doing something dangerous...") | |
401 | + ... raise MyOwnDerivedException(MyOwnDerivedException.ERROR_MISSING_PARAM) | |
402 | + ... except MyOwnDerivedException as e: | |
403 | + ... print(e) | |
404 | + doing something dangerous... | |
405 | + (MyOwnDerivedException): a Parameter is missing | |
406 | + | |
407 | + >>> try: | |
408 | + ... print("doing something dangerous...") | |
409 | + ... raise MyOwnDerivedException(MyOwnDerivedException.ERROR_NEW_CASE2, 'my own special message') | |
410 | + ... except MyImprovedException as e: # we can use superclass also | |
411 | + ... print(e) | |
412 | + doing something dangerous... | |
413 | + (MyOwnDerivedException): Nouveau cas d'erreur 2 ; Specific message: my own special message | |
414 | + | |
415 | + General example : | |
416 | + | |
417 | + >>> try: | |
418 | + ... # do something | |
419 | + ... pass | |
420 | + ... | |
421 | + ... except ValueError: | |
422 | + ... # handle ValueError exception | |
423 | + ... pass | |
424 | + ... | |
425 | + ... except (TypeError, ZeroDivisionError): | |
426 | + ... # handle multiple exceptions | |
427 | + ... # TypeError and ZeroDivisionError | |
428 | + ... pass | |
429 | + ... | |
430 | + ... except: | |
431 | + ... # handle all other exceptions | |
432 | + ... pass | |
433 | + | |
434 | + """ | |
435 | + # Add new specific error cases for this exception type: | |
436 | + ERROR_NEW_CASE1 = "Nouveau cas d'erreur 1" | |
437 | + ERROR_NEW_CASE2 = "Nouveau cas d'erreur 2" | |
438 | + | |
439 | +''' | |
440 | +try: | |
441 | + print("doing something dangerous...") | |
442 | + raise MyOwnDerivedException(MyOwnDerivedException.ERROR_MISSING_PARAM) | |
443 | +except MyOwnDerivedException as e: | |
444 | + print(e) | |
445 | +''' | |
446 | + | |
447 | + | |
302 | 448 | # ''' |
303 | 449 | # ================================================================= |
304 | 450 | # GENERAL CLASSES |
... | ... | @@ -433,6 +579,213 @@ class Person: |
433 | 579 | |
434 | 580 | # ''' |
435 | 581 | # ================================================================= |
582 | +# - CLASS Employee | |
583 | +# ================================================================= | |
584 | +# ''' | |
585 | + | |
586 | +# class typing.NamedTuple : Typed version of collections.namedtuple() | |
587 | + | |
588 | +class Employee(typing.NamedTuple): | |
589 | + """Illustrates usage of collections.namedtuple & typing.NamedTuple | |
590 | + | |
591 | + See https://towardsdatascience.com/what-are-named-tuples-in-python-59dc7bd15680 | |
592 | + | |
593 | + See https://www.netjstech.com/2020/01/named-tuple-python.html | |
594 | + | |
595 | + Usage: | |
596 | + | |
597 | + 1) Here we use typing.NamedTuple : | |
598 | + | |
599 | + >>> andrew = Employee('Andrew', 'Brown', ['Develoer', 'Manager'], 'US') | |
600 | + >>> print(andrew) | |
601 | + Employee(first_name='Andrew', last_name='Brown', jobs=['Develoer', 'Manager'], country='US') | |
602 | + | |
603 | + >>> alice = Employee(first_name='Alice', last_name='Stevenson', jobs=['Product Owner']) | |
604 | + >>> print(alice) | |
605 | + Employee(first_name='Alice', last_name='Stevenson', jobs=['Product Owner'], country='France') | |
606 | + >>> alice.last_name | |
607 | + 'Stevenson' | |
608 | + >>> alice[1] | |
609 | + 'Stevenson' | |
610 | + >>> for attr in alice: print(attr) | |
611 | + Alice | |
612 | + Stevenson | |
613 | + ['Product Owner'] | |
614 | + France | |
615 | + | |
616 | + | |
617 | + 2) Here we define the same Employee as above, but using collections.namedtuple : | |
618 | + | |
619 | + >>> from collections import namedtuple | |
620 | + >>> Employee = namedtuple('Employee', 'first_name last_name jobs country') | |
621 | + | |
622 | + or : | |
623 | + | |
624 | + >>> Employee = namedtuple('Employee', ['first_name', 'last_name', 'jobs', 'country']) | |
625 | + | |
626 | + """ | |
627 | + | |
628 | + first_name: str | |
629 | + last_name: str | |
630 | + jobs: list | |
631 | + country: str = "France" | |
632 | + | |
633 | + | |
634 | +# ''' | |
635 | +# ================================================================= | |
636 | +# - CLASS Point2D | |
637 | +# ================================================================= | |
638 | +# ''' | |
639 | + | |
640 | +# typing.TypedDict : Special construct to add type hints to a dictionary. At runtime it is a plain dict | |
641 | + | |
642 | +class Point2D(typing.TypedDict): | |
643 | + """Class to illustrate typing.TypedDict | |
644 | + | |
645 | + (Special construct to add type hints to a dictionary. At runtime it is a plain dict) | |
646 | + | |
647 | + See: https://adamj.eu/tech/2021/05/10/python-type-hints-how-to-use-typeddict/ | |
648 | + | |
649 | + | |
650 | + Usage: | |
651 | + | |
652 | + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK | |
653 | + | |
654 | + b: Point2D = {'x': 1, 'y': 2} # KO (missing label) | |
655 | + | |
656 | + c: Point2D = {'z': 3, 'label': 'bad'} # KO (z not defined) | |
657 | + | |
658 | + d: Point2D = {} # KO (missing x, y, label) | |
659 | + | |
660 | + | |
661 | + Definition (other possibilities): | |
662 | + | |
663 | + Point2D = TypedDict('Point2D', x=int, y=int, label=str) | |
664 | + | |
665 | + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) | |
666 | + """ | |
667 | + x: int | |
668 | + y: int | |
669 | + label: str | |
670 | + | |
671 | + | |
672 | +def get_point() -> Point2D: | |
673 | + return { | |
674 | + 'x': 1, | |
675 | + 'y': 2, | |
676 | + 'label': 'good' | |
677 | + } | |
678 | + | |
679 | + | |
680 | + | |
681 | +# ''' | |
682 | +# ================================================================= | |
683 | +# - CLASS Position | |
684 | +# ================================================================= | |
685 | +# ''' | |
686 | + | |
687 | +# @dataclass : shortcut for class definition | |
688 | + | |
689 | +from dataclasses import dataclass, field | |
690 | + | |
691 | + | |
692 | +@dataclass | |
693 | +class Position: | |
694 | + """@dataclass | |
695 | + | |
696 | + Class to illustrate Data Classes (@dataclass) | |
697 | + | |
698 | + See: https://realpython.com/python-data-classes | |
699 | + | |
700 | + A DataClass is a shortcut to define a "data structure" without method | |
701 | + | |
702 | + It is much like a NamedTuple, but "mutable", and with more features. | |
703 | + | |
704 | + Defines automatically a lot of things for you : | |
705 | + | |
706 | + - __init__() (with self.x = x, self.y = y, ...) | |
707 | + | |
708 | + - __repr()__ | |
709 | + | |
710 | + - __eq()__ | |
711 | + | |
712 | + - order, sort, immutable or not (frozen=True), ... | |
713 | + | |
714 | + NB: on peut quand mรชme ajouter des mรฉthodes ร une dataclass car cโest une classe normale... | |
715 | + | |
716 | + | |
717 | + Definition alternatives : | |
718 | + | |
719 | + >>> from dataclasses import make_dataclass | |
720 | + >>> Position = make_dataclass('Position', ['name', 'lat', 'lon']) | |
721 | + | |
722 | + Usage: | |
723 | + | |
724 | + >>> pos = Position('Oslo', 10.8, 59.9) | |
725 | + >>> pos | |
726 | + Position(name='Oslo', lat=10.8, lon=59.9) | |
727 | + >>> pos.lon | |
728 | + 59.9 | |
729 | + """ | |
730 | + | |
731 | + name: str | |
732 | + lon: float | |
733 | + lat: float = 0.0 # with default value | |
734 | + | |
735 | + | |
736 | +@dataclass | |
737 | +class PlayingCard: | |
738 | + """@dataclass | |
739 | + | |
740 | + Class to illustrate Data Classes (@dataclass) | |
741 | + """ | |
742 | + rank: str | |
743 | + suit: str | |
744 | + | |
745 | + | |
746 | +@dataclass | |
747 | +class Deck: | |
748 | + """@dataclass | |
749 | + | |
750 | + Class to illustrate Data Classes (@dataclass) | |
751 | + | |
752 | + Usage: | |
753 | + | |
754 | + >>> queen_of_hearts = PlayingCard('Q', 'Hearts') | |
755 | + | |
756 | + >>> ace_of_spades = PlayingCard('A', 'Spades') | |
757 | + | |
758 | + >>> two_cards = Deck([queen_of_hearts, ace_of_spades]) | |
759 | + """ | |
760 | + cards: List[PlayingCard] | |
761 | + | |
762 | + | |
763 | +@dataclass | |
764 | +class Cmd: | |
765 | + """@dataclass | |
766 | + | |
767 | + PyROS example class to illustrate Data Classes | |
768 | + (@dataclass, and using 'field') | |
769 | + | |
770 | + Usage: | |
771 | + | |
772 | + >>> c = Cmd('get_timezone') | |
773 | + | |
774 | + >>> c = Cmd('do_init','do_init'), | |
775 | + """ | |
776 | + | |
777 | + generic_name: str = 'generic name' | |
778 | + native_name: str = '' | |
779 | + desc: str = 'Description' | |
780 | + params: Dict[str, str] = field(default_factory=dict) # equivalent to "= {}" which is not allowed | |
781 | + final_simul_response: str = 'simulator response' | |
782 | + final_device_responses: Dict[str, str] = field(default_factory=dict) | |
783 | + immediate_responses: Dict[str, str] = field(default_factory=dict) | |
784 | + errors: Dict[str, str] = field(default_factory=dict) | |
785 | + | |
786 | + | |
787 | +# ''' | |
788 | +# ================================================================= | |
436 | 789 | # Main function (definition) |
437 | 790 | # ================================================================= |
438 | 791 | # ''' | ... | ... |