Commit 1b109b723c9ab0ff32ddf3e837bab5063525e5b9

Authored by Etienne Pallier
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 # '''
... ...