├── .gitignore ├── LICENSE ├── README.md ├── code-templates ├── class-abstract.py ├── class-concrete.py ├── class-interface.py ├── module.py ├── package__init__.py ├── test-case-class.py └── test-module.py ├── hms-artisan.geany ├── hms-artisan ├── Makefile ├── README.md ├── etc │ └── hms │ │ └── hms_artisan.conf ├── hms_data-ch-10 │ ├── Artisan-data │ │ └── df19a3e7-0a53-4461-9291-541688db8a76.json │ ├── Order-data │ │ └── 0f80749c-8dc4-4643-9f43-de1d345a684f.json │ └── Product-data │ │ └── a577dd5c-03bf-4445-accd-e6bbdee74cf3.json ├── setup.py ├── src │ └── hms_artisan │ │ ├── __init__.py │ │ ├── artisan_objects.py │ │ └── data_storage.py ├── test │ └── test_hms_artisan │ │ ├── .hms_artisan.test-results │ │ ├── __init__.py │ │ ├── hms_artisan.artisan_objects.test-results │ │ ├── hms_artisan.data_storage.test-results │ │ ├── test_artisan_objects.py │ │ └── test_data_storage.py └── var │ └── cache │ └── hms │ ├── artisan │ └── placeholder.txt │ └── placeholder.txt ├── hms-co-app.geany ├── hms-co-app ├── Makefile ├── README.md ├── etc │ └── hms │ │ └── hms_co.conf ├── setup.py ├── src │ └── hms_co │ │ └── __init__.py └── var │ └── cache │ └── hms │ ├── co │ └── placeholder.txt │ └── placeholder.txt ├── hms-core.geany ├── hms-core ├── Makefile ├── README.md ├── ch-10-snippets │ └── sql-data-object.py ├── etc │ ├── hms │ │ └── hms_core.conf │ ├── init.d │ │ └── testdaemon.sh │ └── init │ │ └── testdaemon.conf ├── future │ └── co_objects.py ├── lib │ └── systemd │ │ └── system │ │ └── testdaemon.service ├── miscellany │ └── initial-test-run.txt ├── requirements.txt ├── scratch-space │ ├── HasProducts.py │ ├── callable-object.py │ ├── config-examples │ │ ├── daemons-config-json-yaml.py │ │ ├── example-config.json │ │ └── example-config.yaml │ ├── data-storage-demo.py │ ├── datastories.py │ ├── diamond-problem.py │ ├── dict-args-example.py │ ├── email-checks.py │ ├── list-reference.py │ ├── testdaemond │ └── url-checks.py ├── setup.py ├── src │ └── hms_core │ │ ├── __init__.py │ │ ├── __pycache__ │ │ └── __init__.cpython-35.pyc │ │ ├── business_objects.py │ │ ├── co_objects.py │ │ ├── daemons.py │ │ ├── data_objects.py │ │ └── data_storage.py ├── tests │ ├── run_tests.py │ └── test_hms_core │ │ ├── __init__.py │ │ ├── __pycache__ │ │ └── __init__.cpython-35.pyc │ │ ├── hms_core.business_objects.test-results │ │ ├── hms_core.co_objects.test-results │ │ ├── hms_core.data_objects.test-results │ │ ├── hms_core.data_storage.test-results │ │ ├── hms_core.test-results │ │ ├── test_business_objects.py │ │ ├── test_co_objects.py │ │ ├── test_data_objects.py │ │ └── test_data_storage.py ├── usr │ └── local │ │ └── bin │ │ └── testdaemon.py └── var │ └── cache │ └── hms │ ├── core │ └── placeholder.txt │ └── placeholder.txt ├── hms-gateway.geany ├── hms-gateway ├── Makefile ├── README.md ├── etc │ └── hms │ │ └── hms_gateway.conf ├── requirements.txt ├── scratch-space │ ├── configuration-examples │ │ ├── ini_config.py │ │ ├── myservice.ini │ │ ├── myservice.json │ │ └── myservice.yaml │ ├── logging-examples │ │ ├── example.log │ │ └── logging-example.py │ ├── py_daemon_example.py │ └── py_daemon_func.py ├── setup.py ├── src │ └── hms_gateway │ │ └── __init__.py └── var │ └── cache │ └── hms │ ├── gateway │ └── placeholder.txt │ └── placeholder.txt ├── hms_sys.geany ├── scratch-space ├── .coverage ├── __pycache__ │ ├── me.cpython-35.pyc │ └── test_me.cpython-35.pyc ├── inspect_me.py ├── me.py ├── scope.py ├── test_me.py └── test_me1.py ├── standards └── idic │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-35.pyc │ └── unit_testing.cpython-35.pyc │ └── unit_testing.py └── tree.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore for hms_sys project 2 | # Geany project-files 3 | *.geany 4 | 5 | # Build-folders 6 | */dist/ 7 | *.egg-info 8 | 9 | # __pycache__ folders 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Hands-On Software Engineering with Python 5 | 6 | Hands-On Software Engineering with Python 7 | 8 | This is the code repository for [Hands-On Software Engineering with Python](https://www.packtpub.com/application-development/hands-software-engineering-python?utm_source=github&utm_medium=repository&utm_campaign=9781788622011 ), published by Packt. 9 | 10 | **Move beyond basic programming and construct reliable and efficient software with complex code** 11 | 12 | ## What is this book about? 13 | Software Engineering is about more than just writing code — it includes a host of “soft” skills that apply to almost any development-effort, no matter the language, development-methodology or scope of the project. Being a Senior Developer all but requires awareness of how those skills, along with their expected technical counterparts, mesh together through a project’s life-cycle. This book walks you through that discovery by walking through the entire life-cycle of a multi-tier system and its related software projects. You’ll see what happens before any development takes place, and what impact the decisions and designs made at each step have on the development process. The development of the entire project, over the course of several iterations based on real-world Agile iterations, will be implemented, sometimes starting with nothing, in one of the fastest-growing languages in the world — Python. The details of, and advantages behind a number industry best practices will show why they are best practices. Application of those practices in Python will be laid out, and some Python-specific capabilities that are often overlooked. Finally, the book will, from first principles to complete implementation, implement a High Performance Computing solution. 14 | 15 | This book covers the following exciting features: 16 | Learn what happens over the course of a system’s life (SDLC). 17 | Learn what to expect from the pre-development life-cycle steps. 18 | Find out how the development-specific phases of the SDLC affect development. 19 | Uncover what a real-world development process might be like, in an Agile way. 20 | Find out how to do more than just write the code. 21 | Learn project-independent best practices exist, and how to use them. 22 | Find out how to design and implement a High Performance Computing process. 23 | 24 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1788622014) today! 25 | 26 | https://www.packtpub.com/ 28 | 29 | ## Instructions and Navigations 30 | All of the code is organized into folders. For example, Chapter01. 31 | 32 | The code will look like the following: 33 | ``` 34 | def SetNodeResource(x, y, z, r, v): n = get_node(x,y) n.z = z n.resources.add(r, v) 35 | ``` 36 | 37 | **Following is what you need for this book:** 38 | Hands-On Software Engineering with Python is for you if you are a developer having basic understanding of programming and its paradigms and want to skill up as a senior programmer. It is assumed that you have basic Python knowledge. 39 | 40 | With the following software and hardware list you can run all code files present in the book (Chapter 1-19). 41 | ### Software and Hardware List 42 | | Chapter | Software required | OS required | 43 | | -------- | ------------------------------------ | ----------------------------------- | 44 | | 6-19 | Python 3.5.2 | Windows and Unix (Any) | 45 | | 13 | MongoDB 2.6.10 | Windows and Unix (Any) | 46 | | 16 | RabbitMQ 3.5.7 | Windows and Unix (Any) | 47 | | 18 | NSSM | Windows and Unix (Any) | 48 | 49 | 50 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://www.packtpub.com/sites/default/files/downloads/9781788622011_ColorImages.pdf). 51 | 52 | ### Related products 53 | * Clean Code in Python [[Packt]](https://www.packtpub.com/application-development/clean-code-python?utm_source=github&utm_medium=repository&utm_campaign=9781788835831 ) [[Amazon]](https://www.amazon.com/dp/1788835832) 54 | 55 | * Mastering Python Design Patterns - Second Edition [[Packt]](https://www.packtpub.com/application-development/mastering-python-design-patterns-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788837484 ) [[Amazon]](https://www.amazon.com/dp/1788837487) 56 | 57 | 58 | ## Get to Know the Author 59 | **Brian Allbee** 60 | has been writing programs since the mid-1970s, and started a career in software just as the World Wide Web was starting to take off. He has worked in areas as varied as organization membership management, content/asset management, and process and workflow automation in industries as varied as advertising, consumer health advisement, technical publication, and cloud-computing automation. He has focused exclusively on Python solutions for the best part of a decade 61 | 62 | 63 | ### Suggestions and Feedback 64 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 65 | 66 | ### Download a free PDF 67 | 68 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
69 |

https://packt.link/free-ebook/9781788622011

-------------------------------------------------------------------------------- /code-templates/class-abstract.py: -------------------------------------------------------------------------------- 1 | # Remember to import abc! 2 | 3 | class AbstractClassName(metaclass=abc.ABCMeta): 4 | """TODO: Document the class. 5 | Provides baseline functionality, interface requirements, and 6 | type-identity for objects that can REPRESENT_SOMETHING 7 | """ 8 | ################################### 9 | # Class attributes/constants # 10 | ################################### 11 | 12 | ################################### 13 | # Property-getter methods # 14 | ################################### 15 | 16 | # def _get_property_name(self) -> str: 17 | # return self._property_name 18 | 19 | ################################### 20 | # Property-setter methods # 21 | ################################### 22 | 23 | # def _set_property_name(self, value:str) -> None: 24 | # # TODO: Type- and/or value-check the value argument of the 25 | # # setter-method, unless it's deemed unnecessary. 26 | # self._property_name = value 27 | 28 | ################################### 29 | # Property-deleter methods # 30 | ################################### 31 | 32 | # def _del_property_name(self) -> None: 33 | # self._property_name = None 34 | 35 | ################################### 36 | # Instance property definitions # 37 | ################################### 38 | 39 | # abstract_property = abc.abstractproperty() 40 | 41 | # property_name = property( 42 | # # TODO: Remove setter and deleter if access is not needed 43 | # _get_property_name, _set_property_name, _del_property_name, 44 | # 'Gets, sets or deletes the property_name (str) of the instance' 45 | # ) 46 | 47 | ################################### 48 | # Object initialization # 49 | ################################### 50 | 51 | # TODO: Add and document arguments if/as needed 52 | def __init__(self): 53 | """ 54 | Object initialization. 55 | 56 | self .............. (AbstractClassName instance, required) The instance to 57 | execute against 58 | """ 59 | # - Call parent initializers if needed 60 | # - Set default instance property-values using _del_... methods 61 | # - Set instance property-values from arguments using 62 | # _set_... methods 63 | # - Perform any other initialization needed 64 | pass # Remove this line 65 | 66 | ################################### 67 | # Object deletion # 68 | ################################### 69 | 70 | ################################### 71 | # Abstract methods # 72 | ################################### 73 | 74 | # @abc.abstractmethod 75 | # def instance_method(self, arg:str, *args, **kwargs): 76 | # """TODO: Document method 77 | # DOES_WHATEVER 78 | # 79 | # self .............. (AbstractClassName instance, required) The 80 | # instance to execute against 81 | # arg ............... (str, required) The string argument 82 | # *args ............. (object*, optional) The arglist 83 | # **kwargs .......... (dict, optional) keyword-args, accepts: 84 | # - kwd_arg ........ (type, optional, defaults to SOMETHING) The SOMETHING 85 | # to apply 86 | # """ 87 | # pass 88 | 89 | ################################### 90 | # Instance methods # 91 | ################################### 92 | 93 | # def instance_method(self, arg:str, *args, **kwargs): 94 | # """TODO: Document method 95 | # DOES_WHATEVER 96 | # 97 | # self .............. (AbstractClassName instance, required) The 98 | # instance to execute against 99 | # arg ............... (str, required) The string argument 100 | # *args ............. (object*, optional) The arglist 101 | # **kwargs .......... (dict, optional) keyword-args, accepts: 102 | # - kwd_arg ........ (type, optional, defaults to SOMETHING) The SOMETHING 103 | # to apply 104 | # """ 105 | # pass 106 | 107 | ################################### 108 | # Overrides of built-in methods # 109 | ################################### 110 | 111 | ################################### 112 | # Class methods # 113 | ################################### 114 | 115 | ################################### 116 | # Static methods # 117 | ################################### 118 | 119 | -------------------------------------------------------------------------------- /code-templates/class-concrete.py: -------------------------------------------------------------------------------- 1 | 2 | class ClassName: 3 | """TODO: Document the class. 4 | Represents a WHATEVER 5 | """ 6 | ################################### 7 | # Class attributes/constants # 8 | ################################### 9 | 10 | ################################### 11 | # Property-getter methods # 12 | ################################### 13 | 14 | # def _get_property_name(self) -> str: 15 | # return self._property_name 16 | 17 | ################################### 18 | # Property-setter methods # 19 | ################################### 20 | 21 | # def _set_property_name(self, value:str) -> None: 22 | # # TODO: Type- and/or value-check the value argument of the 23 | # # setter-method, unless it's deemed unnecessary. 24 | # self._property_name = value 25 | 26 | ################################### 27 | # Property-deleter methods # 28 | ################################### 29 | 30 | # def _del_property_name(self) -> None: 31 | # self._property_name = None 32 | 33 | ################################### 34 | # Instance property definitions # 35 | ################################### 36 | 37 | # property_name = property( 38 | # # TODO: Remove setter and deleter if access is not needed 39 | # _get_property_name, _set_property_name, _del_property_name, 40 | # 'Gets, sets or deletes the property_name (str) of the instance' 41 | # ) 42 | 43 | ################################### 44 | # Object initialization # 45 | ################################### 46 | 47 | # TODO: Add and document arguments if/as needed 48 | def __init__(self): 49 | """ 50 | Object initialization. 51 | 52 | self .............. (ClassName instance, required) The instance to 53 | execute against 54 | """ 55 | # - Call parent initializers if needed 56 | # - Set default instance property-values using _del_... methods 57 | # - Set instance property-values from arguments using 58 | # _set_... methods 59 | # - Perform any other initialization needed 60 | pass # Remove this line 61 | 62 | ################################### 63 | # Object deletion # 64 | ################################### 65 | 66 | ################################### 67 | # Instance methods # 68 | ################################### 69 | 70 | # def instance_method(self, arg:str, *args, **kwargs): 71 | # """TODO: Document method 72 | # DOES_WHATEVER 73 | # 74 | # self .............. (ClassName instance, required) The instance to 75 | # execute against 76 | # arg ............... (str, required) The string argument 77 | # *args ............. (object*, optional) The arglist 78 | # **kwargs .......... (dict, optional) keyword-args, accepts: 79 | # - kwd_arg ........ (type, optional, defaults to SOMETHING) The SOMETHING 80 | # to apply 81 | # """ 82 | # pass 83 | 84 | ################################### 85 | # Overrides of built-in methods # 86 | ################################### 87 | 88 | ################################### 89 | # Class methods # 90 | ################################### 91 | 92 | ################################### 93 | # Static methods # 94 | ################################### 95 | 96 | -------------------------------------------------------------------------------- /code-templates/class-interface.py: -------------------------------------------------------------------------------- 1 | # Remember to import abc! 2 | 3 | class InterfaceName(metaclass=abc.ABCMeta): 4 | """TODO: Document the class. 5 | Provides interface requirements, and type-identity for objects that 6 | can REPRESENT_SOMETHING 7 | """ 8 | ################################### 9 | # Class attributes/constants # 10 | ################################### 11 | 12 | ################################### 13 | # Instance property definitions # 14 | ################################### 15 | 16 | # abstract_property = abc.abstractproperty() 17 | 18 | ################################### 19 | # Object initialization # 20 | ################################### 21 | 22 | # TODO: Add and document arguments if/as needed 23 | def __init__(self): 24 | """ 25 | Object initialization. 26 | 27 | self .............. (InterfaceName instance, required) The instance to 28 | execute against 29 | """ 30 | # - Call parent initializers if needed 31 | # - Perform any other initialization needed 32 | pass # Remove this line 33 | 34 | ################################### 35 | # Object deletion # 36 | ################################### 37 | 38 | ################################### 39 | # Abstract methods # 40 | ################################### 41 | 42 | # @abc.abstractmethod 43 | # def instance_method(self, arg:str, *args, **kwargs): 44 | # """TODO: Document method 45 | # DOES_WHATEVER 46 | # 47 | # self .............. (InterfaceName instance, required) The 48 | # instance to execute against 49 | # arg ............... (str, required) The string argument 50 | # *args ............. (object*, optional) The arglist 51 | # **kwargs .......... (dict, optional) keyword-args, accepts: 52 | # - kwd_arg ........ (type, optional, defaults to SOMETHING) The SOMETHING 53 | # to apply 54 | # """ 55 | # pass 56 | 57 | ################################### 58 | # Class methods # 59 | ################################### 60 | 61 | ################################### 62 | # Static methods # 63 | ################################### 64 | 65 | -------------------------------------------------------------------------------- /code-templates/module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | TODO: Document the module. 4 | Provides classes and functionality for SOME_PURPOSE 5 | """ 6 | 7 | ####################################### 8 | # Any needed from __future__ imports # 9 | # Create an "__all__" list to support # 10 | # "from module import member" use # 11 | ####################################### 12 | 13 | __all__ = [ 14 | # Constants 15 | # Exceptions 16 | # Functions 17 | # ABC "interface" classes 18 | # ABC abstract classes 19 | # Concrete classes 20 | ] 21 | 22 | ####################################### 23 | # Module metadata/dunder-names # 24 | ####################################### 25 | 26 | __author__ = 'Brian D. Allbee' 27 | __copyright__ = 'Copyright 2018, all rights reserved' 28 | __status__ = 'Development' 29 | 30 | ####################################### 31 | # Standard library imports needed # 32 | ####################################### 33 | 34 | # Uncomment this if there are abstract classes or "interfaces" 35 | # defined in the module... 36 | # import abc 37 | 38 | ####################################### 39 | # Third-party imports needed # 40 | ####################################### 41 | 42 | ####################################### 43 | # Local imports needed # 44 | ####################################### 45 | 46 | ####################################### 47 | # Initialization needed before member # 48 | # definition can take place # 49 | ####################################### 50 | 51 | ####################################### 52 | # Module-level Constants # 53 | ####################################### 54 | 55 | ####################################### 56 | # Custom Exceptions # 57 | ####################################### 58 | 59 | ####################################### 60 | # Module functions # 61 | ####################################### 62 | 63 | ####################################### 64 | # ABC "interface" classes # 65 | ####################################### 66 | 67 | ####################################### 68 | # Abstract classes # 69 | ####################################### 70 | 71 | ####################################### 72 | # Concrete classes # 73 | ####################################### 74 | 75 | ####################################### 76 | # Initialization needed after member # 77 | # definition is complete # 78 | ####################################### 79 | 80 | ####################################### 81 | # Imports needed after member # 82 | # definition (to resolve circular # 83 | # dependencies - avoid if at all # 84 | # possible # 85 | ####################################### 86 | 87 | ####################################### 88 | # Code to execute if the module is # 89 | # called directly # 90 | ####################################### 91 | 92 | if __name__ == '__main__': 93 | pass 94 | -------------------------------------------------------------------------------- /code-templates/package__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | TODO: Document the package. 4 | Package-header for the PACKAGE_NAMESPACE namespace. 5 | Provides classes and functionality for SOME_PURPOSE 6 | """ 7 | 8 | ####################################### 9 | # Any needed from __future__ imports # 10 | # Create an "__all__" list to support # 11 | # "from module import member" use # 12 | ####################################### 13 | 14 | __all__ = [ 15 | # Constants 16 | # Exceptions 17 | # Functions 18 | # ABC "interface" classes 19 | # ABC abstract classes 20 | # Concrete classes 21 | # Child packages and modules 22 | ] 23 | 24 | ####################################### 25 | # Module metadata/dunder-names # 26 | ####################################### 27 | 28 | __author__ = 'Brian D. Allbee' 29 | __copyright__ = 'Copyright 2018, all rights reserved' 30 | __status__ = 'Development' 31 | 32 | ####################################### 33 | # Standard library imports needed # 34 | ####################################### 35 | 36 | ####################################### 37 | # Third-party imports needed # 38 | ####################################### 39 | 40 | # Uncomment this if there are abstract classes or "interfaces" 41 | # defined in the module... 42 | # import abc 43 | 44 | ####################################### 45 | # Local imports needed # 46 | ####################################### 47 | 48 | ####################################### 49 | # Initialization needed before member # 50 | # definition can take place # 51 | ####################################### 52 | 53 | ####################################### 54 | # Module-level Constants # 55 | ####################################### 56 | 57 | ####################################### 58 | # Custom Exceptions # 59 | ####################################### 60 | 61 | ####################################### 62 | # Module functions # 63 | ####################################### 64 | 65 | ####################################### 66 | # ABC "interface" classes # 67 | ####################################### 68 | 69 | ####################################### 70 | # Abstract classes # 71 | ####################################### 72 | 73 | ####################################### 74 | # Concrete classes # 75 | ####################################### 76 | 77 | ####################################### 78 | # Initialization needed after member # 79 | # definition is complete # 80 | ####################################### 81 | 82 | ####################################### 83 | # Imports needed after member # 84 | # definition (to resolve circular # 85 | # dependencies - avoid if at all # 86 | # possible # 87 | ####################################### 88 | 89 | ####################################### 90 | # Code to execute if the module is # 91 | # called directly # 92 | ####################################### 93 | 94 | if __name__ == '__main__': 95 | pass 96 | -------------------------------------------------------------------------------- /code-templates/test-case-class.py: -------------------------------------------------------------------------------- 1 | @testModuleNameCodeCoverage.AddMethodTesting 2 | @testModuleNameCodeCoverage.AddPropertyTesting 3 | class testClassName(unittest.TestCase): 4 | 5 | ################################### 6 | # Tests of class methods # 7 | ################################### 8 | 9 | def testmethod_name(self): 10 | # Tests the method_name method of the ClassName class 11 | # - Test all permutations of "good" argument-values: 12 | # - Test all permutations of each "bad" argument-value 13 | # set against "good" values for the other arguments: 14 | self.fail('testmethod_name is not yet implemented') 15 | 16 | ################################### 17 | # Tests of class properties # 18 | ################################### 19 | 20 | # def testproperty_name(self): 21 | # # Tests the property_name property of the ClassName class 22 | # # - Assert that the getter is correct: 23 | # self.assertEqual( 24 | # ClassName.property_name.fget, 25 | # ClassName._get_property_name, 26 | # 'ClassName.property_name is expected to use the ' 27 | # '_get_property_name method as its getter-method' 28 | # ) 29 | # # - If property_name is not expected to be publicly settable, 30 | # # the second item here (ClassName._set_property_name) should 31 | # # be changed to None, and the failure message adjusted 32 | # # accordingly: 33 | # # - Assert that the setter is correct: 34 | # self.assertEqual( 35 | # ClassName.property_name.fset, 36 | # ClassName._set_property_name, 37 | # 'ClassName.property_name is expected to use the ' 38 | # '_set_property_name method as its setter-method' 39 | # ) 40 | # # - If property_name is not expected to be publicly deletable, 41 | # # the second item here (ClassName._del_property_name) should 42 | # # be changed to None, and the failure message adjusted 43 | # # accordingly: 44 | # # - Assert that the deleter is correct: 45 | # self.assertEqual( 46 | # ClassName.property_name.fdel, 47 | # ClassName._del_property_name, 48 | # 'ClassName.property_name is expected to use the ' 49 | # '_del_property_name method as its deleter-method' 50 | # ) 51 | 52 | LocalSuite.addTests( 53 | unittest.TestLoader().loadTestsFromTestCase( 54 | testClassName 55 | ) 56 | ) 57 | 58 | -------------------------------------------------------------------------------- /code-templates/test-module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Python unit-test-module template. Copy the template to a new 4 | # unit-test-module location, and start replacing names as needed: 5 | # 6 | # PackagePath ==> The path/namespace of the parent of the module/package 7 | # being tested in this file. 8 | # ModuleName ==> The name of the module being tested 9 | # 10 | # Then remove this comment-block 11 | 12 | """ 13 | Defines unit-tests for the module at PackagePath.ModuleName. 14 | """ 15 | 16 | ####################################### 17 | # Any needed from __future__ imports # 18 | # Create an "__all__" list to support # 19 | # "from module import member" use # 20 | ####################################### 21 | 22 | __all__ = [ 23 | # Test-case classes 24 | # Child test-modules 25 | ] 26 | 27 | ####################################### 28 | # Module metadata/dunder-names # 29 | ####################################### 30 | 31 | __author__ = 'Brian D. Allbee' 32 | __copyright__ = 'Copyright 2018, all rights reserved' 33 | __status__ = 'Development' 34 | 35 | ####################################### 36 | # Standard library imports needed # 37 | ####################################### 38 | 39 | import os 40 | import sys 41 | import unittest 42 | 43 | ####################################### 44 | # Third-party imports needed # 45 | ####################################### 46 | 47 | ####################################### 48 | # Local imports needed # 49 | ####################################### 50 | 51 | from idic.unit_testing import * 52 | 53 | ####################################### 54 | # Initialization needed before member # 55 | # definition can take place # 56 | ####################################### 57 | 58 | ####################################### 59 | # Module-level Constants # 60 | ####################################### 61 | 62 | LocalSuite = unittest.TestSuite() 63 | 64 | ####################################### 65 | # Import the module being tested # 66 | ####################################### 67 | 68 | import PackagePath.ModuleName as ModuleName 69 | from PackagePath.ModuleName import * 70 | 71 | ####################################### 72 | # Code-coverage test-case and # 73 | # decorator-methods # 74 | ####################################### 75 | 76 | class testModuleNameCodeCoverage(ModuleCoverageTest): 77 | _testNamespace = 'PackagePath' 78 | _testModule = ModuleName 79 | 80 | LocalSuite.addTests( 81 | unittest.TestLoader().loadTestsFromTestCase( 82 | testModuleNameCodeCoverage 83 | ) 84 | ) 85 | 86 | ####################################### 87 | # Test-cases in the module # 88 | ####################################### 89 | 90 | ####################################### 91 | # Child-module test-cases to execute # 92 | ####################################### 93 | 94 | # import child_module 95 | # LocalSuite.addTests(child_module.LocalSuite._tests) 96 | 97 | ####################################### 98 | # Imports to resolve circular # 99 | # dependencies. Avoid if possible. # 100 | ####################################### 101 | 102 | ####################################### 103 | # Initialization that needs to # 104 | # happen after member definition. # 105 | ####################################### 106 | 107 | ####################################### 108 | # Code to execute if file is called # 109 | # or run directly. # 110 | ####################################### 111 | 112 | if __name__ == '__main__': 113 | import time 114 | results = unittest.TestResult() 115 | testStartTime = time.time() 116 | LocalSuite.run(results) 117 | results.runTime = time.time() - testStartTime 118 | PrintTestResults(results) 119 | if not results.errors and not results.failures: 120 | SaveTestReport(results, 'PackagePath.ModuleName', 121 | 'PackagePath.ModuleName.test-results') 122 | -------------------------------------------------------------------------------- /hms-artisan.geany: -------------------------------------------------------------------------------- 1 | [editor] 2 | line_wrapping=false 3 | line_break_column=72 4 | auto_continue_multiline=true 5 | 6 | [file_prefs] 7 | final_new_line=true 8 | ensure_convert_new_lines=false 9 | strip_trailing_spaces=false 10 | replace_tabs=false 11 | 12 | [indentation] 13 | indent_width=4 14 | indent_type=0 15 | indent_hard_tab_width=8 16 | detect_indent=true 17 | detect_indent_width=false 18 | indent_mode=2 19 | 20 | [project] 21 | name=HMS Artisan Application 22 | base_path=/home/ballbee/git/hms_sys/hms-artisan 23 | description= 24 | file_patterns= 25 | 26 | [long line marker] 27 | long_line_behaviour=1 28 | long_line_column=72 29 | 30 | [files] 31 | current_page=13 32 | FILE_NAME_0=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fmodule.py;0;4 33 | FILE_NAME_1=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fpackage__init__.py;0;4 34 | FILE_NAME_2=3994;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-abstract.py;0;4 35 | FILE_NAME_3=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-concrete.py;0;4 36 | FILE_NAME_4=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-interface.py;0;4 37 | FILE_NAME_5=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Ftest-module.py;0;4 38 | FILE_NAME_6=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Ftest-case-class.py;0;4 39 | FILE_NAME_7=174;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2FREADME.md;0;4 40 | FILE_NAME_8=566;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Fsrc%2Fhms_artisan%2F__init__.py;0;4 41 | FILE_NAME_9=9715;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Fsrc%2Fhms_artisan%2Fartisan_objects.py;0;4 42 | FILE_NAME_10=12580;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Fsrc%2Fhms_artisan%2Fdata_storage.py;0;4 43 | FILE_NAME_11=2872;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Ftest%2Ftest_hms_artisan%2F__init__.py;0;4 44 | FILE_NAME_12=49439;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Ftest%2Ftest_hms_artisan%2Ftest_artisan_objects.py;0;4 45 | FILE_NAME_13=9901;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Ftest%2Ftest_hms_artisan%2Ftest_data_storage.py;0;4 46 | 47 | [VTE] 48 | last_dir=/home/ballbee/git/hms_sys 49 | 50 | [prjorg] 51 | source_patterns=*.c;*.C;*.cpp;*.cxx;*.c++;*.cc;*.m; 52 | header_patterns=*.h;*.H;*.hpp;*.hxx;*.h++;*.hh; 53 | ignored_dirs_patterns=.*;CVS; 54 | ignored_file_patterns=*.o;*.obj;*.a;*.lib;*.so;*.dll;*.lo;*.la;*.class;*.jar;*.pyc;*.mo;*.gmo; 55 | generate_tag_prefs=0 56 | external_dirs= 57 | 58 | [build-menu] 59 | EX_00_LB=_Execute 60 | EX_00_CM=PYTHONPATH=~/git/hms_sys/hms-artisan/src/:~/git/hms_sys/hms-core/src/:~/git/hms_sys/standards/:~/py_envs/hms/core/lib/python3.5/site-packages/ ~/py_envs/hms/artisan/bin/python "%f" 61 | EX_00_WD= 62 | -------------------------------------------------------------------------------- /hms-artisan/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the HMS Artisan Application (hms-artisan) project 2 | 3 | main: test setup 4 | # Doesn't (yet) do anything other than running the test and 5 | # setup targets 6 | 7 | setup: 8 | # Calls the main setup.py to build a source-distribution 9 | # python setup.py sdist 10 | 11 | test: 12 | # Executes the unit-tests for the package, allowing the build- 13 | # process to die and stop the build if a test fails 14 | -------------------------------------------------------------------------------- /hms-artisan/README.md: -------------------------------------------------------------------------------- 1 | # HMS Artisan Application 2 | 3 | Provides an executable local application that allows an Artisan to 4 | manage Products and Orders, communicating with the Central Office as 5 | needed. 6 | 7 | ## Python Virtual Environment 8 | 9 | **Create:** `python3 -m venv ~/py_envs/hms/artisan` 10 | **Activate:** `source ~/py_envs/hms/artisan/bin/activate` 11 | **Upgrade pip:** `pip install --upgrade pip` 12 | 13 | ## Build process 14 | 15 | `python3 setup.py sdist` (source-distribution) 16 | 17 | -------------------------------------------------------------------------------- /hms-artisan/etc/hms/hms_artisan.conf: -------------------------------------------------------------------------------- 1 | # A placeholder for the config-file for hms_artisan 2 | -------------------------------------------------------------------------------- /hms-artisan/hms_data-ch-10/Artisan-data/df19a3e7-0a53-4461-9291-541688db8a76.json: -------------------------------------------------------------------------------- 1 | { 2 | "contact_name": "John Smith", 3 | "company_name": null, 4 | "is_active": true, 5 | "is_deleted": false, 6 | "modified": "2018-05-20 12:50:39", 7 | "contact_email": "j@smith.com", 8 | "website": null, 9 | "address": { 10 | "building_address": null, 11 | "city": "City Name", 12 | "street_address": "12345 Main Street", 13 | "region": null, 14 | "postal_code": null, 15 | "country": null 16 | }, 17 | "created": "2018-05-20 12:50:39", 18 | "oid": "df19a3e7-0a53-4461-9291-541688db8a76" 19 | } -------------------------------------------------------------------------------- /hms-artisan/hms_data-ch-10/Order-data/0f80749c-8dc4-4643-9f43-de1d345a684f.json: -------------------------------------------------------------------------------- 1 | { 2 | "city": "city", 3 | "street_address": "street address", 4 | "region": null, 5 | "is_deleted": false, 6 | "country": null, 7 | "name": "name", 8 | "is_active": true, 9 | "oid": "0f80749c-8dc4-4643-9f43-de1d345a684f", 10 | "building_address": null, 11 | "items": {}, 12 | "postal_code": null, 13 | "modified": "2018-05-20 12:50:39", 14 | "created": "2018-05-20 12:50:39" 15 | } -------------------------------------------------------------------------------- /hms-artisan/hms_data-ch-10/Product-data/a577dd5c-03bf-4445-accd-e6bbdee74cf3.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": null, 3 | "dimensions": null, 4 | "is_deleted": false, 5 | "available": true, 6 | "summary": "summary", 7 | "name": "name", 8 | "is_active": true, 9 | "oid": "a577dd5c-03bf-4445-accd-e6bbdee74cf3", 10 | "metadata": {}, 11 | "store_available": true, 12 | "shipping_weight": 0, 13 | "modified": "2018-05-20 12:50:39", 14 | "created": "2018-05-20 12:50:39" 15 | } -------------------------------------------------------------------------------- /hms-artisan/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | # The actual setup function call: 6 | setup( 7 | name='HMS-Artisan-Application', 8 | version='0.1.dev0', 9 | author='Brian D. Allbee', 10 | description='', 11 | package_dir={ 12 | '':'src', 13 | # ... 14 | }, 15 | # Can also be automatically generated using 16 | # setuptools.find_packages... 17 | packages=[ 18 | 'hms_artisan', 19 | # ... 20 | ], 21 | package_data={ 22 | # 'hms_artisan':[ 23 | # 'filename.ext', 24 | # # ... 25 | # ] 26 | }, 27 | entry_points={ 28 | # 'console_scripts':[ 29 | # 'executable_name = namespace.path:function', 30 | # # ... 31 | # ], 32 | }, 33 | ) 34 | -------------------------------------------------------------------------------- /hms-artisan/src/hms_artisan/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | TODO: Document the package. 4 | Package-header for the hms_artisan namespace. 5 | Provides classes and functionality for SOME_PURPOSE 6 | """ 7 | 8 | ####################################### 9 | # Any needed from __future__ imports # 10 | # Create an "__all__" list to support # 11 | # "from module import member" use # 12 | ####################################### 13 | 14 | __all__ = [ 15 | # Constants 16 | # Exceptions 17 | # Functions 18 | # ABC "interface" classes 19 | # ABC abstract classes 20 | # Concrete classes 21 | # Child packages and modules 22 | 'artisan_objects', 23 | 'data_storage', 24 | ] 25 | 26 | ####################################### 27 | # Module metadata/dunder-names # 28 | ####################################### 29 | 30 | __author__ = 'Brian D. Allbee' 31 | __copyright__ = 'Copyright 2018, all rights reserved' 32 | __status__ = 'Development' 33 | 34 | ####################################### 35 | # Standard library imports needed # 36 | ####################################### 37 | 38 | ####################################### 39 | # Third-party imports needed # 40 | ####################################### 41 | 42 | # Uncomment this if there are abstract classes or "interfaces" 43 | # defined in the module... 44 | # import abc 45 | 46 | ####################################### 47 | # Local imports needed # 48 | ####################################### 49 | 50 | ####################################### 51 | # Initialization needed before member # 52 | # definition can take place # 53 | ####################################### 54 | 55 | ####################################### 56 | # Module-level Constants # 57 | ####################################### 58 | 59 | ####################################### 60 | # Custom Exceptions # 61 | ####################################### 62 | 63 | ####################################### 64 | # Module functions # 65 | ####################################### 66 | 67 | ####################################### 68 | # ABC "interface" classes # 69 | ####################################### 70 | 71 | ####################################### 72 | # Abstract classes # 73 | ####################################### 74 | 75 | ####################################### 76 | # Concrete classes # 77 | ####################################### 78 | 79 | ####################################### 80 | # Initialization needed after member # 81 | # definition is complete # 82 | ####################################### 83 | 84 | ####################################### 85 | # Imports needed after member # 86 | # definition (to resolve circular # 87 | # dependencies - avoid if at all # 88 | # possible # 89 | ####################################### 90 | 91 | ####################################### 92 | # Code to execute if the module is # 93 | # called directly # 94 | ####################################### 95 | 96 | if __name__ == '__main__': 97 | pass 98 | -------------------------------------------------------------------------------- /hms-artisan/test/test_hms_artisan/.hms_artisan.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: .hms_artisan 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 113 7 | Number of tests skipped ... 7 8 | Tests ran in .......... 0.248 seconds 9 | #------------------------------------------------------------------------------# 10 | List of skipped tests and the reasons they were skipped: 11 | testsort (test_artisan_objects.testArtisan) 12 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 13 | testsort (test_artisan_objects.testOrder) 14 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 15 | testsort (test_artisan_objects.testProduct) 16 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 17 | test_load_objects (test_data_storage.testJSONFileDataObject) 18 | - Since the file-load process provided by _load_objects is used by mahy of the CRUD operations, it is tested as part of testCRUDOperations 19 | testdelete (test_data_storage.testJSONFileDataObject) 20 | - Since deleting a data-file is part of the CRUD operations, it is tested as part of testCRUDOperations 21 | testget (test_data_storage.testJSONFileDataObject) 22 | - Since reading data-files is part of the CRUD operations, it is tested as part of testCRUDOperations 23 | testsave (test_data_storage.testJSONFileDataObject) 24 | - Since creating a data-file is part of the CRUD operations, it is tested as part of testCRUDOperations 25 | ################################################################################ -------------------------------------------------------------------------------- /hms-artisan/test/test_hms_artisan/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Defines unit-tests for the module at .hms_artisan. 5 | """ 6 | 7 | ####################################### 8 | # Any needed from __future__ imports # 9 | # Create an "__all__" list to support # 10 | # "from module import member" use # 11 | ####################################### 12 | 13 | __all__ = [ 14 | # Test-case classes 15 | # Child test-modules 16 | ] 17 | 18 | ####################################### 19 | # Module metadata/dunder-names # 20 | ####################################### 21 | 22 | __author__ = 'Brian D. Allbee' 23 | __copyright__ = 'Copyright 2018, all rights reserved' 24 | __status__ = 'Development' 25 | 26 | ####################################### 27 | # Standard library imports needed # 28 | ####################################### 29 | 30 | import os 31 | import sys 32 | import unittest 33 | 34 | ####################################### 35 | # Third-party imports needed # 36 | ####################################### 37 | 38 | ####################################### 39 | # Local imports needed # 40 | ####################################### 41 | 42 | from idic.unit_testing import * 43 | 44 | ####################################### 45 | # Initialization needed before member # 46 | # definition can take place # 47 | ####################################### 48 | 49 | ####################################### 50 | # Module-level Constants # 51 | ####################################### 52 | 53 | LocalSuite = unittest.TestSuite() 54 | 55 | ####################################### 56 | # Import the module being tested # 57 | ####################################### 58 | 59 | import hms_artisan as hms_artisan 60 | from hms_artisan import * 61 | 62 | ####################################### 63 | # Code-coverage test-case and # 64 | # decorator-methods # 65 | ####################################### 66 | 67 | class testhms_artisanCodeCoverage(ModuleCoverageTest): 68 | _testNamespace = '' 69 | _testModule = hms_artisan 70 | 71 | LocalSuite.addTests( 72 | unittest.TestLoader().loadTestsFromTestCase( 73 | testhms_artisanCodeCoverage 74 | ) 75 | ) 76 | 77 | ####################################### 78 | # Test-cases in the module # 79 | ####################################### 80 | 81 | ####################################### 82 | # Child-module test-cases to execute # 83 | ####################################### 84 | 85 | import test_artisan_objects 86 | LocalSuite.addTests(test_artisan_objects.LocalSuite._tests) 87 | 88 | import test_data_storage 89 | LocalSuite.addTests(test_data_storage.LocalSuite._tests) 90 | 91 | # import child_module 92 | # LocalSuite.addTests(child_module.LocalSuite._tests) 93 | 94 | ####################################### 95 | # Imports to resolve circular # 96 | # dependencies. Avoid if possible. # 97 | ####################################### 98 | 99 | ####################################### 100 | # Initialization that needs to # 101 | # happen after member definition. # 102 | ####################################### 103 | 104 | ####################################### 105 | # Code to execute if file is called # 106 | # or run directly. # 107 | ####################################### 108 | 109 | if __name__ == '__main__': 110 | import time 111 | results = unittest.TestResult() 112 | testStartTime = time.time() 113 | LocalSuite.run(results) 114 | results.runTime = time.time() - testStartTime 115 | PrintTestResults(results) 116 | if not results.errors and not results.failures: 117 | SaveTestReport(results, '.hms_artisan', 118 | '.hms_artisan.test-results') 119 | -------------------------------------------------------------------------------- /hms-artisan/test/test_hms_artisan/hms_artisan.artisan_objects.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_artisan.artisan_objects 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 100 7 | Number of tests skipped ... 3 8 | Tests ran in .......... 0.240 seconds 9 | #------------------------------------------------------------------------------# 10 | List of skipped tests and the reasons they were skipped: 11 | testsort (__main__.testArtisan) 12 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 13 | testsort (__main__.testOrder) 14 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 15 | testsort (__main__.testProduct) 16 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 17 | ################################################################################ -------------------------------------------------------------------------------- /hms-artisan/test/test_hms_artisan/hms_artisan.data_storage.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_artisan.data_storage 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 12 7 | Number of tests skipped ... 4 8 | Tests ran in .......... 0.004 seconds 9 | #------------------------------------------------------------------------------# 10 | List of skipped tests and the reasons they were skipped: 11 | test_load_objects (__main__.testJSONFileDataObject) 12 | - Since the file-load process provided by _load_objects is used by mahy of the CRUD operations, it is tested as part of testCRUDOperations 13 | testdelete (__main__.testJSONFileDataObject) 14 | - Since deleting a data-file is part of the CRUD operations, it is tested as part of testCRUDOperations 15 | testget (__main__.testJSONFileDataObject) 16 | - Since reading data-files is part of the CRUD operations, it is tested as part of testCRUDOperations 17 | testsave (__main__.testJSONFileDataObject) 18 | - Since creating a data-file is part of the CRUD operations, it is tested as part of testCRUDOperations 19 | ################################################################################ -------------------------------------------------------------------------------- /hms-artisan/test/test_hms_artisan/test_data_storage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Defines unit-tests for the module at hms_artisan.data_storage. 5 | """ 6 | 7 | ####################################### 8 | # Any needed from __future__ imports # 9 | # Create an "__all__" list to support # 10 | # "from module import member" use # 11 | ####################################### 12 | 13 | __all__ = [ 14 | # Test-case classes 15 | # Child test-modules 16 | ] 17 | 18 | ####################################### 19 | # Module metadata/dunder-names # 20 | ####################################### 21 | 22 | __author__ = 'Brian D. Allbee' 23 | __copyright__ = 'Copyright 2018, all rights reserved' 24 | __status__ = 'Development' 25 | 26 | ####################################### 27 | # Standard library imports needed # 28 | ####################################### 29 | 30 | import os 31 | import sys 32 | import unittest 33 | 34 | from datetime import datetime 35 | from uuid import UUID, uuid4 36 | 37 | import os 38 | from shutil import rmtree 39 | 40 | from hms_core.data_objects import BaseDataObject 41 | 42 | ####################################### 43 | # Third-party imports needed # 44 | ####################################### 45 | 46 | ####################################### 47 | # Local imports needed # 48 | ####################################### 49 | 50 | from idic.unit_testing import * 51 | 52 | ####################################### 53 | # Initialization needed before member # 54 | # definition can take place # 55 | ####################################### 56 | 57 | ####################################### 58 | # Module-level Constants # 59 | ####################################### 60 | 61 | LocalSuite = unittest.TestSuite() 62 | 63 | ####################################### 64 | # Import the module being tested # 65 | ####################################### 66 | 67 | import hms_artisan.data_storage as data_storage 68 | from hms_artisan.data_storage import * 69 | 70 | ####################################### 71 | # Constants for test-methods # 72 | ####################################### 73 | 74 | GoodDateTimes = [ 75 | # - actual datetime values 76 | datetime.now(), datetime.fromtimestamp(1234567890), 77 | datetime.now().timestamp(), 78 | # - timestamp numbers 79 | 1234567890, 1234567890.123456, 80 | # - strings 81 | '2001-01-01 12:34:56', '3001-01-01 12:34:56', 82 | '1911-01-01 12:34:56', 83 | # - A parsed datetime 84 | datetime.strptime( 85 | '2001-01-01 12:34:56', JSONFileDataObject._data_time_string 86 | ), 87 | # - datetimes outside the UNIX epoch, just in case 88 | datetime.strptime( 89 | '3001-01-01 12:34:56', JSONFileDataObject._data_time_string 90 | ), 91 | datetime.strptime( 92 | '1911-01-01 12:34:56', JSONFileDataObject._data_time_string 93 | ), 94 | ] 95 | 96 | ####################################### 97 | # Code-coverage test-case and # 98 | # decorator-methods # 99 | ####################################### 100 | 101 | class testdata_storageCodeCoverage(ModuleCoverageTest): 102 | _testNamespace = 'hms_artisan' 103 | _testModule = data_storage 104 | 105 | LocalSuite.addTests( 106 | unittest.TestLoader().loadTestsFromTestCase( 107 | testdata_storageCodeCoverage 108 | ) 109 | ) 110 | 111 | ####################################### 112 | # Test-cases in the module # 113 | ####################################### 114 | 115 | class JSONFileDataObjectDerived(JSONFileDataObject): 116 | _file_store_dir = '/tmp/hms_artisan_test' 117 | 118 | def matches(self, **criteria) -> (bool,): 119 | return JSONFileDataObject.matches(self, **criteria) 120 | 121 | def to_data_dict(self): 122 | return { 123 | 'created':datetime.strftime( 124 | self.created, self.__class__._data_time_string 125 | ), 126 | 'is_active':self.is_active, 127 | 'is_deleted':self.is_deleted, 128 | 'modified':datetime.strftime( 129 | self.modified, self.__class__._data_time_string 130 | ), 131 | 'oid':str(self.oid), 132 | } 133 | 134 | @classmethod 135 | def from_data_dict(cls, data_dict:(dict,)): 136 | return cls(**data_dict) 137 | 138 | @classmethod 139 | def sort(cls, objects, sort_by): 140 | pass 141 | 142 | class NoFileStoreDir(JSONFileDataObject): 143 | 144 | def matches(self, **criteria) -> (bool,): 145 | return JSONFileDataObject.matches(self, **criteria) 146 | 147 | def to_data_dict(self): 148 | return { 149 | 'created':datetime.strftime( 150 | self.created, self.__class__._data_time_string 151 | ), 152 | 'is_active':self.is_active, 153 | 'is_deleted':self.is_deleted, 154 | 'modified':datetime.strftime( 155 | self.modified, self.__class__._data_time_string 156 | ), 157 | 'oid':str(self.oid), 158 | } 159 | 160 | @classmethod 161 | def from_data_dict(cls, data_dict:(dict,)): 162 | return cls(**data_dict) 163 | 164 | @classmethod 165 | def sort(cls, objects, sort_by): 166 | pass 167 | 168 | @testdata_storageCodeCoverage.AddMethodTesting 169 | @testdata_storageCodeCoverage.AddPropertyTesting 170 | class testJSONFileDataObject(unittest.TestCase): 171 | 172 | ################################### 173 | # Tests of class methods # 174 | ################################### 175 | 176 | def test__init__(self): 177 | # Tests the __init__ method of the JSONFileDataObject class 178 | # - All we need to do here is prove that the various 179 | # setter- and deleter-method calls are operating as 180 | # expected -- same as BaseDataObject 181 | # - deleters first 182 | test_object = JSONFileDataObjectDerived() 183 | self.assertEquals(test_object._created, None) 184 | self.assertEquals(test_object._is_active, True) 185 | self.assertEquals(test_object._is_deleted, False) 186 | self.assertEquals(test_object._is_dirty, False) 187 | self.assertEquals(test_object._is_new, True) 188 | self.assertEquals(test_object._modified, None) 189 | self.assertEquals(test_object._oid, None) 190 | # - setters 191 | oid = uuid4() 192 | created = GoodDateTimes[0] 193 | modified = GoodDateTimes[1] 194 | is_active = False 195 | is_deleted = True 196 | is_dirty = True 197 | is_new = False 198 | test_object = JSONFileDataObjectDerived( 199 | oid, created, modified, is_active, is_deleted, 200 | is_dirty, is_new 201 | ) 202 | self.assertEquals(test_object.oid, oid) 203 | self.assertEquals(test_object.created, created) 204 | self.assertEquals(test_object.is_active, is_active) 205 | self.assertEquals(test_object.is_deleted, is_deleted) 206 | self.assertEquals(test_object.is_dirty, is_dirty) 207 | self.assertEquals(test_object.is_new, is_new) 208 | self.assertEquals(test_object.modified, modified) 209 | 210 | 211 | def test_create(self): 212 | # Tests the _create method of the JSONFileDataObject class 213 | test_object = JSONFileDataObjectDerived() 214 | try: 215 | test_object._create() 216 | self.fail( 217 | 'JSONFileDataObject is not expected to raise ' 218 | 'NotImplementedError on a call to _create' 219 | ) 220 | except NotImplementedError: 221 | pass 222 | except Exception as error: 223 | self.fail( 224 | 'JSONFileDataObject is not expected to raise ' 225 | 'NotImplementedError on a call to _create, but %s ' 226 | 'was raised instead:\n - %s' % 227 | (error.__class__.__name__, error) 228 | ) 229 | 230 | @unittest.skip( 231 | 'Since the file-load process provided by _load_objects is ' 232 | 'used by mahy of the CRUD operations, it is tested as part of ' 233 | 'testCRUDOperations' 234 | ) 235 | def test_load_objects(self): 236 | # Tests the _load_objects method of the JSONFileDataObject class 237 | self.fail('test_load_objects is not yet implemented') 238 | 239 | def test_update(self): 240 | # Tests the _update method of the JSONFileDataObject class 241 | test_object = JSONFileDataObjectDerived() 242 | try: 243 | test_object._update() 244 | self.fail( 245 | 'JSONFileDataObject is not expected to raise ' 246 | 'NotImplementedError on a call to _update' 247 | ) 248 | except NotImplementedError: 249 | pass 250 | except Exception as error: 251 | self.fail( 252 | 'JSONFileDataObject is not expected to raise ' 253 | 'NotImplementedError on a call to _update, but %s ' 254 | 'was raised instead:\n - %s' % 255 | (error.__class__.__name__, error) 256 | ) 257 | 258 | @unittest.skip( 259 | 'Since deleting a data-file is part of the CRUD operations, ' 260 | 'it is tested as part of testCRUDOperations' 261 | ) 262 | def testdelete(self): 263 | # Tests the delete method of the JSONFileDataObject class 264 | self.fail('testdelete is not yet implemented') 265 | 266 | @unittest.skip( 267 | 'Since reading data-files is part of the CRUD operations, ' 268 | 'it is tested as part of testCRUDOperations' 269 | ) 270 | def testget(self): 271 | # Tests the get method of the JSONFileDataObject class 272 | self.fail('testget is not yet implemented') 273 | 274 | @unittest.skip( 275 | 'Since creating a data-file is part of the CRUD operations, ' 276 | 'it is tested as part of testCRUDOperations' 277 | ) 278 | def testsave(self): 279 | # Tests the save method of the JSONFileDataObject class 280 | self.fail('testsave is not yet implemented') 281 | 282 | def testCRUDOperations(self): 283 | # - First, assure that the class-level data-object collection 284 | # (in JSONFileDataObjectDerived._loaded_objects) is None, 285 | # and that the file-repository does not exist. 286 | JSONFileDataObjectDerived._loaded_objects = None 287 | if os.path.exists(JSONFileDataObjectDerived._file_store_dir): 288 | rmtree(JSONFileDataObjectDerived._file_store_dir) 289 | # - Next, create an item and save it 290 | first_object = JSONFileDataObjectDerived() 291 | first_object.save() 292 | # - Verify that the file exists where we're expecting it 293 | self.assertTrue( 294 | os.path.exists( 295 | '/tmp/hms_artisan_test/JSONFileDataObjectDerived-' 296 | 'data/%s.json' % first_object.oid 297 | ) 298 | ) 299 | # - and that it exists in the in-memory cache 300 | self.assertNotEqual( 301 | JSONFileDataObjectDerived._loaded_objects.get( 302 | first_object.oid 303 | ), None 304 | ) 305 | # - Verify that the item can be retrieved, and has the same 306 | # data 307 | first_object_get = JSONFileDataObjectDerived.get()[0] 308 | self.assertTrue( 309 | first_object.matches(**first_object_get.to_data_dict()) 310 | ) 311 | self.assertEqual( 312 | first_object.is_dirty, first_object_get.is_dirty 313 | ) 314 | self.assertEqual( 315 | first_object.is_new, first_object_get.is_new 316 | ) 317 | # - Create and save two more items 318 | second_object = JSONFileDataObjectDerived() 319 | second_object.save() 320 | third_object = JSONFileDataObjectDerived() 321 | third_object.save() 322 | # - Verify that all three items can be retrieved, and that 323 | # they are the expected objects, at least by their oids: 324 | # Those, as part of the file-names, *will* be unique and 325 | # distinct... 326 | all_objects = JSONFileDataObjectDerived.get() 327 | expected = set( 328 | [o.oid for o in [first_object, second_object, third_object]] 329 | ) 330 | actual = set([o.oid for o in all_objects]) 331 | self.assertEqual(expected, actual) 332 | # - Verify that the file for the second item exists, so the 333 | # verification later of its deletion is a valid test 334 | self.assertTrue( 335 | os.path.exists( 336 | '/tmp/hms_artisan_test/JSONFileDataObjectDerived-' 337 | 'data/%s.json' % second_object.oid 338 | ) 339 | ) 340 | # - Delete the second item 341 | JSONFileDataObjectDerived.delete(second_object.oid) 342 | # - Verify that the item has been removed from the loaded- 343 | # object store and from the file-system 344 | self.assertEqual( 345 | JSONFileDataObjectDerived._loaded_objects.get(second_object.oid), 346 | None 347 | ) 348 | self.assertFalse( 349 | os.path.exists( 350 | '/tmp/hms_artisan_test/JSONFileDataObjectDerived-' 351 | 'data/%s.json' % second_object.oid 352 | ) 353 | ) 354 | # - Update the last object created, and save it 355 | third_object._set_is_active(False) 356 | third_object._set_is_deleted(True) 357 | third_object.save() 358 | # - Read the updated object and verify that the changes made 359 | # were saved to the file. 360 | third_object_get = JSONFileDataObjectDerived.get(third_object.oid)[0] 361 | self.assertEqual( 362 | third_object.to_data_dict(), 363 | third_object_get.to_data_dict() 364 | ) 365 | self.assertTrue( 366 | third_object.matches(**third_object_get.to_data_dict()) 367 | ) 368 | self.assertEqual( 369 | third_object.is_dirty, third_object_get.is_dirty 370 | ) 371 | self.assertEqual( 372 | third_object.is_new, third_object_get.is_new 373 | ) 374 | # - Since other test-methods down the line might need to start 375 | # with empty object- and file-sets, re-clear them both 376 | JSONFileDataObjectDerived._loaded_objects = None 377 | if os.path.exists(JSONFileDataObjectDerived._file_store_dir): 378 | rmtree(JSONFileDataObjectDerived._file_store_dir) 379 | 380 | ################################### 381 | # Tests of class properties # 382 | ################################### 383 | 384 | def test_file_store_dir(self): 385 | self.assertEqual( 386 | JSONFileDataObject._file_store_dir, None, 387 | 'JSONFileDataObject._file_store_dir is expected to provide ' 388 | 'a None default value that must be overridden by derived ' 389 | 'classes, but it is set to "%s" (%s)' % 390 | ( 391 | JSONFileDataObject._file_store_dir, 392 | type(JSONFileDataObject._file_store_dir).__name__ 393 | ) 394 | ) 395 | try: 396 | test_object = NoFileStoreDir() 397 | self.fail( 398 | 'Classes derived from JSONFileDataObject are expected ' 399 | 'to define a _file_store_dir class-attribute, or cause ' 400 | 'instantiation of objects from classes that don\'t ' 401 | 'have one defined to fail with an AttributeError' 402 | ) 403 | except AttributeError: 404 | pass 405 | 406 | # def testproperty_name(self): 407 | # # Tests the property_name property of the JSONFileDataObject class 408 | # # - Assert that the getter is correct: 409 | # self.assertEqual( 410 | # JSONFileDataObject.property_name.fget, 411 | # JSONFileDataObject._get_property_name, 412 | # 'JSONFileDataObject.property_name is expected to use the ' 413 | # '_get_property_name method as its getter-method' 414 | # ) 415 | # # - If property_name is not expected to be publicly settable, 416 | # # the second item here (JSONFileDataObject._set_property_name) should 417 | # # be changed to None, and the failure message adjusted 418 | # # accordingly: 419 | # # - Assert that the setter is correct: 420 | # self.assertEqual( 421 | # JSONFileDataObject.property_name.fset, 422 | # JSONFileDataObject._set_property_name, 423 | # 'JSONFileDataObject.property_name is expected to use the ' 424 | # '_set_property_name method as its setter-method' 425 | # ) 426 | # # - If property_name is not expected to be publicly deletable, 427 | # # the second item here (JSONFileDataObject._del_property_name) should 428 | # # be changed to None, and the failure message adjusted 429 | # # accordingly: 430 | # # - Assert that the deleter is correct: 431 | # self.assertEqual( 432 | # JSONFileDataObject.property_name.fdel, 433 | # JSONFileDataObject._del_property_name, 434 | # 'JSONFileDataObject.property_name is expected to use the ' 435 | # '_del_property_name method as its deleter-method' 436 | # ) 437 | 438 | LocalSuite.addTests( 439 | unittest.TestLoader().loadTestsFromTestCase( 440 | testJSONFileDataObject 441 | ) 442 | ) 443 | 444 | ####################################### 445 | # Child-module test-cases to execute # 446 | ####################################### 447 | 448 | # import child_module 449 | # LocalSuite.addTests(child_module.LocalSuite._tests) 450 | 451 | ####################################### 452 | # Imports to resolve circular # 453 | # dependencies. Avoid if possible. # 454 | ####################################### 455 | 456 | ####################################### 457 | # Initialization that needs to # 458 | # happen after member definition. # 459 | ####################################### 460 | 461 | ####################################### 462 | # Code to execute if file is called # 463 | # or run directly. # 464 | ####################################### 465 | 466 | if __name__ == '__main__': 467 | import time 468 | results = unittest.TestResult() 469 | testStartTime = time.time() 470 | LocalSuite.run(results) 471 | results.runTime = time.time() - testStartTime 472 | PrintTestResults(results) 473 | if not results.errors and not results.failures: 474 | SaveTestReport(results, 'hms_artisan.data_storage', 475 | 'hms_artisan.data_storage.test-results') 476 | -------------------------------------------------------------------------------- /hms-artisan/var/cache/hms/artisan/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms-artisan 2 | -------------------------------------------------------------------------------- /hms-artisan/var/cache/hms/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms applications and services 2 | -------------------------------------------------------------------------------- /hms-co-app.geany: -------------------------------------------------------------------------------- 1 | [editor] 2 | line_wrapping=false 3 | line_break_column=72 4 | auto_continue_multiline=true 5 | 6 | [file_prefs] 7 | final_new_line=true 8 | ensure_convert_new_lines=false 9 | strip_trailing_spaces=false 10 | replace_tabs=false 11 | 12 | [indentation] 13 | indent_width=4 14 | indent_type=0 15 | indent_hard_tab_width=8 16 | detect_indent=true 17 | detect_indent_width=false 18 | indent_mode=2 19 | 20 | [project] 21 | name=HMS Central Office Application 22 | base_path=/home/ballbee/git/hms_sys/hms-co-app 23 | description= 24 | file_patterns= 25 | 26 | [long line marker] 27 | long_line_behaviour=1 28 | long_line_column=72 29 | 30 | [files] 31 | current_page=7 32 | FILE_NAME_0=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-abstract.py;0;4 33 | FILE_NAME_1=3096;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-concrete.py;0;4 34 | FILE_NAME_2=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-interface.py;0;4 35 | FILE_NAME_3=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fmodule.py;0;4 36 | FILE_NAME_4=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fpackage__init__.py;0;4 37 | FILE_NAME_5=198;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-co-app%2FREADME.md;0;4 38 | FILE_NAME_6=528;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-co-app%2Fsrc%2Fhms_co%2F__init__.py;0;4 39 | FILE_NAME_7=46644;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fbusiness_objects.py;0;4 40 | FILE_NAME_8=16293;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fdata_objects.py;0;4 41 | FILE_NAME_9=25697;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fdata_storage.py;0;4 42 | FILE_NAME_10=34463;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-artisan%2Fsrc%2Fhms_artisan%2Fartisan_objects.py;0;4 43 | 44 | [VTE] 45 | last_dir=/home/ballbee/Dropbox/projects (1)/vehicle_logbook/vlogbook 46 | 47 | [prjorg] 48 | source_patterns=*.c;*.C;*.cpp;*.cxx;*.c++;*.cc;*.m; 49 | header_patterns=*.h;*.H;*.hpp;*.hxx;*.h++;*.hh; 50 | ignored_dirs_patterns=.*;CVS; 51 | ignored_file_patterns=*.o;*.obj;*.a;*.lib;*.so;*.dll;*.lo;*.la;*.class;*.jar;*.pyc;*.mo;*.gmo; 52 | generate_tag_prefs=0 53 | external_dirs= 54 | 55 | [build-menu] 56 | EX_00_LB=_Execute 57 | EX_00_CM=PYTHONPATH=~/git/hms_sys/hms-co/src/:~/git/hms_sys/hms-core/src/:~/git/hms_sys/standards/:~/py_envs/hms/core/lib/python3.5/site-packages/ ~/py_envs/hms/co/bin/python "%f" 58 | EX_00_WD= 59 | -------------------------------------------------------------------------------- /hms-co-app/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the HMS Central Office Application (hms-co) project 2 | 3 | main: test setup 4 | # Doesn't (yet) do anything other than running the test and 5 | # setup targets 6 | 7 | setup: 8 | # Calls the main setup.py to build a source-distribution 9 | # python setup.py sdist 10 | 11 | test: 12 | # Executes the unit-tests for the package, allowing the build- 13 | # process to die and stop the build if a test fails 14 | -------------------------------------------------------------------------------- /hms-co-app/README.md: -------------------------------------------------------------------------------- 1 | # HMS Central Office Application 2 | 3 | Provides an executable application that allows Central Office staff to 4 | perform various tasks that require communication with an Artisan about 5 | Products, Orders, and perhaps other items as well. 6 | 7 | ## Python Virtual Environment 8 | 9 | **Create:** `python3 -m venv ~/py_envs/hms/co` 10 | **Activate:** `source ~/py_envs/hms/co/bin/activate` 11 | **Upgrade pip:** `pip install --upgrade pip` 12 | 13 | ## Build process 14 | 15 | `python3 setup.py sdist` (source-distribution) 16 | -------------------------------------------------------------------------------- /hms-co-app/etc/hms/hms_co.conf: -------------------------------------------------------------------------------- 1 | # A placeholder for the config-file for hms_co 2 | -------------------------------------------------------------------------------- /hms-co-app/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | # The actual setup function call: 6 | setup( 7 | name='HMS-Central-Office-Application', 8 | version='0.1.dev0', 9 | author='Brian D. Allbee', 10 | description='', 11 | package_dir={ 12 | 'hms_co':'src', 13 | # ... 14 | }, 15 | # Can also be automatically generated using 16 | # setuptools.find_packages... 17 | packages=[ 18 | 'hms_co', 19 | # ... 20 | ], 21 | package_data={ 22 | # 'hms_co':[ 23 | # 'filename.ext', 24 | # # ... 25 | # ] 26 | }, 27 | entry_points={ 28 | # 'console_scripts':[ 29 | # 'executable_name = namespace.path:function', 30 | # # ... 31 | # ], 32 | }, 33 | ) 34 | -------------------------------------------------------------------------------- /hms-co-app/src/hms_co/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | TODO: Document the package. 4 | Package-header for the hms_co namespace. 5 | Provides classes and functionality for SOME_PURPOSE 6 | """ 7 | 8 | ####################################### 9 | # Any needed from __future__ imports # 10 | # Create an "__all__" list to support # 11 | # "from module import member" use # 12 | ####################################### 13 | 14 | __all__ = [ 15 | # Constants 16 | # Exceptions 17 | # Functions 18 | # ABC "interface" classes 19 | # ABC abstract classes 20 | # Concrete classes 21 | # Child packages and modules 22 | ] 23 | 24 | ####################################### 25 | # Module metadata/dunder-names # 26 | ####################################### 27 | 28 | __author__ = 'Brian D. Allbee' 29 | __copyright__ = 'Copyright 2018, all rights reserved' 30 | __status__ = 'Development' 31 | 32 | ####################################### 33 | # Standard library imports needed # 34 | ####################################### 35 | 36 | ####################################### 37 | # Third-party imports needed # 38 | ####################################### 39 | 40 | # Uncomment this if there are abstract classes or "interfaces" 41 | # defined in the module... 42 | # import abc 43 | 44 | ####################################### 45 | # Local imports needed # 46 | ####################################### 47 | 48 | ####################################### 49 | # Initialization needed before member # 50 | # definition can take place # 51 | ####################################### 52 | 53 | ####################################### 54 | # Module-level Constants # 55 | ####################################### 56 | 57 | ####################################### 58 | # Custom Exceptions # 59 | ####################################### 60 | 61 | ####################################### 62 | # Module functions # 63 | ####################################### 64 | 65 | ####################################### 66 | # ABC "interface" classes # 67 | ####################################### 68 | 69 | ####################################### 70 | # Abstract classes # 71 | ####################################### 72 | 73 | ####################################### 74 | # Concrete classes # 75 | ####################################### 76 | 77 | ####################################### 78 | # Initialization needed after member # 79 | # definition is complete # 80 | ####################################### 81 | 82 | ####################################### 83 | # Imports needed after member # 84 | # definition (to resolve circular # 85 | # dependencies - avoid if at all # 86 | # possible # 87 | ####################################### 88 | 89 | ####################################### 90 | # Code to execute if the module is # 91 | # called directly # 92 | ####################################### 93 | 94 | if __name__ == '__main__': 95 | pass 96 | -------------------------------------------------------------------------------- /hms-co-app/var/cache/hms/co/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms-co 2 | -------------------------------------------------------------------------------- /hms-co-app/var/cache/hms/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms applications and services 2 | -------------------------------------------------------------------------------- /hms-core.geany: -------------------------------------------------------------------------------- 1 | [editor] 2 | line_wrapping=false 3 | line_break_column=72 4 | auto_continue_multiline=true 5 | 6 | [file_prefs] 7 | final_new_line=true 8 | ensure_convert_new_lines=false 9 | strip_trailing_spaces=false 10 | replace_tabs=false 11 | 12 | [indentation] 13 | indent_width=4 14 | indent_type=0 15 | indent_hard_tab_width=8 16 | detect_indent=true 17 | detect_indent_width=false 18 | indent_mode=2 19 | 20 | [project] 21 | name=HMS Core 22 | base_path=/home/ballbee/git/hms_sys/hms-core 23 | description= 24 | file_patterns= 25 | 26 | [long line marker] 27 | long_line_behaviour=1 28 | long_line_column=72 29 | 30 | [files] 31 | current_page=30 32 | FILE_NAME_0=3994;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-abstract.py;0;4 33 | FILE_NAME_1=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-concrete.py;0;4 34 | FILE_NAME_2=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-interface.py;0;4 35 | FILE_NAME_3=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fmodule.py;0;4 36 | FILE_NAME_4=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fpackage__init__.py;0;4 37 | FILE_NAME_5=1064;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Ftest-module.py;0;4 38 | FILE_NAME_6=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Ftest-case-class.py;0;4 39 | FILE_NAME_7=404;Make;0;EUTF-8;1;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2FMakefile;0;4 40 | FILE_NAME_8=267;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2FREADME.md;0;4 41 | FILE_NAME_9=31;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Frequirements.txt;0;4 42 | FILE_NAME_10=762;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsetup.py;0;4 43 | FILE_NAME_11=48;Conf;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fetc%2Fhms%2Fhms_core.conf;0;4 44 | FILE_NAME_12=0;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fvar%2Fcache%2Fhms%2Fplaceholder.txt;0;4 45 | FILE_NAME_13=35;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fvar%2Fcache%2Fhms%2Fcore%2Fplaceholder.txt;0;4 46 | FILE_NAME_14=503;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2F__init__.py;0;4 47 | FILE_NAME_15=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fbusiness_objects.py;0;4 48 | FILE_NAME_16=9281;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fco_objects.py;0;4 49 | FILE_NAME_17=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fdaemons.py;0;4 50 | FILE_NAME_18=14839;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fdata_objects.py;0;4 51 | FILE_NAME_19=17507;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fsrc%2Fhms_core%2Fdata_storage.py;0;4 52 | FILE_NAME_20=17635;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fstandards%2Fidic%2Funit_testing.py;0;4 53 | FILE_NAME_21=391;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Ftests%2Ftest_hms_core%2F__init__.py;0;4 54 | FILE_NAME_22=27466;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Ftests%2Ftest_hms_core%2Ftest_business_objects.py;0;4 55 | FILE_NAME_23=6693;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Ftests%2Ftest_hms_core%2Ftest_co_objects.py;0;4 56 | FILE_NAME_24=6393;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Ftests%2Ftest_hms_core%2Ftest_data_objects.py;0;4 57 | FILE_NAME_25=36287;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Ftests%2Ftest_hms_core%2Ftest_data_storage.py;0;4 58 | FILE_NAME_26=1079;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Ftests%2Frun_tests.py;0;4 59 | FILE_NAME_27=553;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fscratch-space%2Fcallable-object.py;0;4 60 | FILE_NAME_28=0;Conf;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fetc%2Finit%2Ftestdaemon.conf;0;4 61 | FILE_NAME_29=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Fusr%2Flocal%2Fbin%2Ftestdaemon.py;0;4 62 | FILE_NAME_30=232;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-core%2Flib%2Fsystemd%2Fsystem%2Ftestdaemon.service;0;4 63 | 64 | [VTE] 65 | last_dir=/home/ballbee/git/hms_sys/hms-core 66 | 67 | [prjorg] 68 | source_patterns=*.c;*.C;*.cpp;*.cxx;*.c++;*.cc;*.m; 69 | header_patterns=*.h;*.H;*.hpp;*.hxx;*.h++;*.hh; 70 | ignored_dirs_patterns=.*;CVS; 71 | ignored_file_patterns=*.o;*.obj;*.a;*.lib;*.so;*.dll;*.lo;*.la;*.class;*.jar;*.pyc;*.mo;*.gmo; 72 | generate_tag_prefs=0 73 | external_dirs= 74 | 75 | [build-menu] 76 | EX_00_LB=_Execute 77 | EX_00_CM=PYTHONPATH=~/git/hms_sys/hms-core/src/:~/git/hms_sys/standards/ ~/py_envs/hms/core/bin/python "%f" 78 | EX_00_WD= 79 | -------------------------------------------------------------------------------- /hms-core/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the HMS Core (hms-core) project 2 | TMPDIR=/tmp/build/hms_core_build 3 | 4 | local: setup 5 | # Doesn't (yet) do anything other than running the test and 6 | # setup targets 7 | 8 | setup: 9 | # Calls the main setup.py to build a source-distribution 10 | ~/py_envs/hms/core/bin/python setup.py sdist 11 | 12 | unit_test: 13 | # Executes the unit-tests for the package, allowing the build- 14 | # process to die and stop the build if a test fails 15 | ~/py_envs/hms/core/bin/python setup.py test 16 | 17 | build_dir: 18 | # Creates a temporary build-directory, copies the project-files 19 | # to it. 20 | # Creating "$(TMPDIR)" 21 | mkdir -p $(TMPDIR) 22 | # Copying project-files to $(TMPDIR) 23 | cp -R bin $(TMPDIR) 24 | cp -R etc $(TMPDIR) 25 | cp -R src $(TMPDIR) 26 | cp -R var $(TMPDIR) 27 | cp setup.py $(TMPDIR) 28 | 29 | dev_prep: 30 | # Renames any dev-specific files so that they will be the "real" 31 | # files included in the build. 32 | # At this point, there are none, so we'll just exit 33 | 34 | dev: unit_test build_dir dev_prep 35 | # A make-target that generates a build intended to be deployed 36 | # to a shared development environment. 37 | cd $(TMPDIR);~/py_envs/hms/core/bin/python setup.py sdist 38 | 39 | -------------------------------------------------------------------------------- /hms-core/README.md: -------------------------------------------------------------------------------- 1 | # HMS Core 2 | 3 | A class-library collecting all of the baseline business-object 4 | definition to provide representations of objects like Artisans, 5 | Products and Orders. 6 | 7 | ## Python Virtual Environment 8 | 9 | **Create:** `python3 -m venv ~/py_envs/hms/core` 10 | **Activate:** `source ~/py_envs/hms/core/bin/activate` 11 | **Upgrade pip:** `pip install --upgrade pip` 12 | 13 | ## Build process 14 | 15 | `python3 setup.py sdist` (source-distribution) 16 | -------------------------------------------------------------------------------- /hms-core/ch-10-snippets/sql-data-object.py: -------------------------------------------------------------------------------- 1 | # NOTE: This is an example class, NOT INTENDED FOR USE YET! 2 | class HMSSQLDataObject(BaseDataObject, metaclass=abc.ABCMeta): 3 | """ 4 | Provides baseline functionality, interface requirements, and 5 | type-identity for objects that can persist their state-data to 6 | a (GENERIC) SQL-based RDBMS back-end data-store. 7 | """ 8 | ################################### 9 | # Class attributes/constants # 10 | ################################### 11 | 12 | # - Keeps track of the global configuration for data-access 13 | _configuration = None 14 | # - Keeps track of the keys allowed for object-creation from 15 | # retrieved data 16 | _data_dict_keys = None 17 | # - SQL for various expected CRUD actions: 18 | _sql_create = """Some SQL string goes here""" 19 | _sql_read_oids = """Some SQL string goes here""" 20 | _sql_read_all = """Some SQL string goes here""" 21 | _sql_read_criteria = """Some SQL string goes here""" 22 | _sql_update = """Some SQL string goes here""" 23 | _sql_delete = """Some SQL string goes here""" 24 | 25 | ################################### 26 | # Property-getter methods # 27 | ################################### 28 | 29 | def _get_connection(self): 30 | try: 31 | return self.__class__._connection 32 | except AttributeError: 33 | # - Most RDBMS libraries provide a "connect" function, or 34 | # allow the creation of a "connection" object, using the 35 | # parameters we've named in DatastoreConfig, or simple 36 | # variations of them, so all we need to do is connect: 37 | self.__class__._connection = RDBMS.connect( 38 | **self.configuration 39 | ) 40 | return self.__class__._connection 41 | 42 | ################################### 43 | # Property-deleter methods # 44 | ################################### 45 | 46 | def _del_connection(self) -> None: 47 | try: 48 | del self.__class__._connection 49 | except AttributeError: 50 | # - It may already not exist 51 | pass 52 | 53 | ################################### 54 | # Instance property definitions # 55 | ################################### 56 | 57 | connection = property( 58 | _get_connection, None, _del_connection, 59 | 'Gets or deletes the database-connection that the instance ' 60 | 'will use to manage its persistent state-data' 61 | ) 62 | 63 | ################################### 64 | # Object initialization # 65 | ################################### 66 | 67 | def __init__(self, 68 | oid:(UUID,str,None)=None, 69 | created:(datetime,str,float,int,None)=None, 70 | modified:(datetime,str,float,int,None)=None, 71 | is_active:(bool,int,None)=None, 72 | is_deleted:(bool,int,None)=None, 73 | is_dirty:(bool,int,None)=None, 74 | is_new:(bool,int,None)=None, 75 | ): 76 | """ 77 | Object initialization. 78 | 79 | self .............. (HMSMongoDataObject instance, required) The 80 | instance to execute against 81 | oid ............... (UUID|str, optional, defaults to None) The unique 82 | identifier of the object's state-data record in the 83 | back-end data-store 84 | created ........... (datetime|str|float|int, optional, defaults to None) 85 | The date/time that the object was created 86 | modified .......... (datetime|str|float|int, optional, defaults to None) 87 | The date/time that the object was last modified 88 | is_active ......... (bool|int, optional, defaults to None) A flag 89 | indicating that the object is active 90 | is_deleted ........ (bool|int, optional, defaults to None) A flag 91 | indicating that the object should be considered 92 | deleted (and may be in the near future) 93 | is_dirty .......... (bool|int, optional, defaults to None) A flag 94 | indicating that the object's data needs to be 95 | updated in the back-end data-store 96 | is_new ............ (bool|int, optional, defaults to None) A flag 97 | indicating that the object's data needs to be 98 | created in the back-end data-store 99 | """ 100 | # - Call parent initializers if needed 101 | BaseDataObject.__init__(self, 102 | oid, created, modified, is_active, is_deleted, 103 | is_dirty, is_new 104 | ) 105 | # - Perform any other initialization needed 106 | 107 | ################################### 108 | # Object deletion # 109 | ################################### 110 | 111 | ################################### 112 | # Abstract methods # 113 | ################################### 114 | 115 | ################################### 116 | # Instance methods # 117 | ################################### 118 | 119 | def _create(self): 120 | # - The base SQL is in self.__class__._sql_create, and the 121 | # field-values would be retrieved from self.to_data_dict(): 122 | data_dict = self.to_data_dict() 123 | SQL = self.__class__._sql_create 124 | # - Some process would have to add the values, if not the keys, 125 | # into the SQL, and the result sanitized, but once that was 126 | # done, it'd become a simple query-execution: 127 | self.connection.execute(SQL) 128 | 129 | def _update(self): 130 | # - The base SQL is in self.__class__._sql_update, and the 131 | # field-values would be retrieved from self.to_data_dict(): 132 | data_dict = self.to_data_dict() 133 | SQL = self.__class__._sql_update 134 | # - Some process would have to add the values, if not the keys, 135 | # into the SQL, and the result sanitized, but once that was 136 | # done, it'd become a simple query-execution: 137 | self.connection.execute(SQL) 138 | 139 | ################################### 140 | # Overrides of built-in methods # 141 | ################################### 142 | 143 | ################################### 144 | # Class methods # 145 | ################################### 146 | 147 | @classmethod 148 | def delete(cls, *oids): 149 | # - First, we need the database-connection that we're 150 | # working with: 151 | connection = cls.get_connection() 152 | SQL = cls._sql_delete % oids 153 | # - Don't forget to sanitize it before executing it! 154 | result_set = connection.execute(SQL) 155 | 156 | @classmethod 157 | def get(cls, *oids, **criteria) -> list: 158 | # - First, we need the database-connection that we're 159 | # working with: 160 | connection = cls.get_connection() 161 | # - The first pass of the process retrieves documents based 162 | # on oids or criteria. 163 | # - We also need to keep track of whether or not to do a 164 | # matches call on the results after the initial data- 165 | # retrieval: 166 | post_filter = False 167 | # - Records are often returned as a tuple (result_set) 168 | # of tuples (rows) of tuples (field-name, field-value): 169 | # ( ..., ( ('field-name', 'value' ), (...), ... ), ...) 170 | if oids: 171 | # - Need to replace any placeholder values in the raw SQL 172 | # with actual values, AND sanitize the SQL string, but 173 | # it starts with the SQL in cls._sql_read_oids 174 | SQL = cls._sql_read_oids 175 | result_set = connection.execute(SQL) 176 | if criteria: 177 | post_filter = True 178 | elif criteria: 179 | # - The same sort of replacement would need to happen here 180 | # as happens for oids, above. If the query uses just 181 | # one criteria key/value pair initially, we can use the 182 | # match-based filtering later to filter further as needed 183 | key = criteria.keys()[0] 184 | value = criteria[key] 185 | SQL = cls._sql_read_criteria % (key, value) 186 | result_set = connection.execute(SQL) 187 | if len(criteria) > 1: 188 | post_filter = True 189 | else: 190 | SQL = cls._sql_read_all 191 | result_set = connection.execute(SQL) 192 | # - We should have a result_set value here, so we can convert 193 | # it from the tuple of tuples of tuples (or whatever) into 194 | # data_dict-compatible dictionaries: 195 | data_dicts = [ 196 | dict( 197 | [field_tuple for field_tuple in row] 198 | ) 199 | for row in result_set 200 | ] 201 | # - With those, we can create the initial list of instances: 202 | results = [ 203 | cls.from_data_dict(data_dict) 204 | for data_dict in data_dicts 205 | ] 206 | # - If post_filter has been set to True, then the request 207 | # was for items by oid *and* that have certain criteria 208 | if post_filter: 209 | results = [ 210 | obj for obj in results if obj.matches(**criteria) 211 | ] 212 | # - Data-objects that have related child items, like the 213 | # Artisan to Product relationship, may need to acquire 214 | # those children here before returning the results. If 215 | # they do, then a structure like this should work most 216 | # of the time: 217 | # for artisan in results: 218 | # artisan._set_products( 219 | # Product.get(artisan_oid=artisan.oid) 220 | # ) 221 | return results 222 | 223 | ################################### 224 | # Static methods # 225 | ################################### 226 | 227 | -------------------------------------------------------------------------------- /hms-core/etc/hms/hms_core.conf: -------------------------------------------------------------------------------- 1 | # A placeholder for the config-file for hms_core 2 | -------------------------------------------------------------------------------- /hms-core/etc/init.d/testdaemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # - The action we're concerned with appears as $1 in a standard 4 | # bash-script 5 | case $1 in 6 | start) 7 | echo "Starting $0" 8 | /usr/bin/python /usr/local/bin/testdaemon.py 9 | ;; 10 | stop) 11 | echo "Stopping $0" 12 | /usr/bin/pkill -f testdaemon.py 13 | ;; 14 | restart) 15 | echo "Restarting $0" 16 | /usr/bin/pkill -f testdaemon.py 17 | /usr/bin/python /usr/local/bin/testdaemon.py 18 | ;; 19 | esac 20 | -------------------------------------------------------------------------------- /hms-core/etc/init/testdaemon.conf: -------------------------------------------------------------------------------- 1 | description "The testdaemon service, a simple service example written in Python" 2 | 3 | # Start in normal runlevels when disks are mounted and networking 4 | start on runlevel [2345] 5 | 6 | # Stop on shutdown/halt, single-user mode, reboot 7 | stop on runlevel [016] 8 | 9 | env statedir=/var/cache/testdaemon 10 | 11 | # Create the cache directory 12 | pre-start /usr/bin/mkdir -p "$statedir" 13 | 14 | # Start 15 | exec /home/ballbee/py_envs/hms/core/bin/python /home/ballbee/git/hms_sys/hms-core/src/hms_core/daemons.py 16 | -------------------------------------------------------------------------------- /hms-core/future/co_objects.py: -------------------------------------------------------------------------------- 1 | # These are stubs for data-object classes that aren't "in play" yet. 2 | 3 | class Customer(BaseCustomer, HMSMongoDataObject): 4 | """ 5 | Represents a Customer in the context of the central office 6 | applications and services 7 | """ 8 | ################################### 9 | # Class attributes/constants # 10 | ################################### 11 | 12 | ################################### 13 | # Property-getter methods # 14 | ################################### 15 | 16 | # def _get_property_name(self) -> str: 17 | # return self._property_name 18 | 19 | ################################### 20 | # Property-setter methods # 21 | ################################### 22 | 23 | # def _set_property_name(self, value:str) -> None: 24 | # # TODO: Type- and/or value-check the value argument of the 25 | # # setter-method, unless it's deemed unnecessary. 26 | # self._property_name = value 27 | 28 | ################################### 29 | # Property-deleter methods # 30 | ################################### 31 | 32 | # def _del_property_name(self) -> None: 33 | # self._property_name = None 34 | 35 | ################################### 36 | # Instance property definitions # 37 | ################################### 38 | 39 | # property_name = property( 40 | # # TODO: Remove setter and deleter if access is not needed 41 | # _get_property_name, _set_property_name, _del_property_name, 42 | # 'Gets, sets or deletes the property_name (str) of the instance' 43 | # ) 44 | 45 | ################################### 46 | # Object initialization # 47 | ################################### 48 | 49 | # TODO: Add and document arguments if/as needed 50 | def __init__(self, 51 | # - Arguments from HMSMongoDataObject 52 | oid:(UUID,str,None)=None, 53 | created:(datetime,str,float,int,None)=None, 54 | modified:(datetime,str,float,int,None)=None, 55 | is_active:(bool,int,None)=None, 56 | is_deleted:(bool,int,None)=None, 57 | is_dirty:(bool,int,None)=None, 58 | is_new:(bool,int,None)=None, 59 | ): 60 | """ 61 | Object initialization. 62 | 63 | self .............. (Customer instance, required) The instance to 64 | execute against 65 | 66 | oid ............... (UUID|str, optional, defaults to None) The unique 67 | identifier of the object's state-data record in the 68 | back-end data-store 69 | created ........... (datetime|str|float|int, optional, defaults to None) 70 | The date/time that the object was created 71 | modified .......... (datetime|str|float|int, optional, defaults to None) 72 | The date/time that the object was last modified 73 | is_active ......... (bool|int, optional, defaults to None) A flag 74 | indicating that the object is active 75 | is_deleted ........ (bool|int, optional, defaults to None) A flag 76 | indicating that the object should be considered 77 | deleted (and may be in the near future) 78 | is_dirty .......... (bool|int, optional, defaults to None) A flag 79 | indicating that the object's data needs to be 80 | updated in the back-end data-store 81 | is_new ............ (bool|int, optional, defaults to None) A flag 82 | indicating that the object's data needs to be 83 | created in the back-end data-store 84 | """ 85 | # - Call parent initializers if needed 86 | BaseCustomer.__init__() 87 | HMSMongoDataObject.__init__(self, 88 | oid, created, modified, is_active, is_deleted, 89 | is_dirty, is_new 90 | ) 91 | # - Set default instance property-values using _del_... methods 92 | # - Set instance property-values from arguments using 93 | # _set_... methods 94 | # - Perform any other initialization needed 95 | pass # Remove this line 96 | 97 | ################################### 98 | # Object deletion # 99 | ################################### 100 | 101 | ################################### 102 | # Instance methods # 103 | ################################### 104 | 105 | def matches(self, **criteria) -> (bool,): 106 | return HMSMongoDataObject.matches(self, **criteria) 107 | 108 | def to_data_dict(self): 109 | return { 110 | # - BaseCustomer-derived items 111 | # - BaseDataObject-derived items 112 | 'created':datetime.strftime( 113 | self.created, self.__class__._data_time_string 114 | ), 115 | 'is_active':self.is_active, 116 | 'is_deleted':self.is_deleted, 117 | 'modified':datetime.strftime( 118 | self.modified, self.__class__._data_time_string 119 | ), 120 | 'oid':str(self.oid), 121 | } 122 | 123 | ################################### 124 | # Overrides of built-in methods # 125 | ################################### 126 | 127 | ################################### 128 | # Class methods # 129 | ################################### 130 | 131 | @classmethod 132 | def sort(cls, objects:(list,tuple), sort_by:(str,)) -> (list,): 133 | return BaseDataObject.sort(cls, objects, sort_by) 134 | 135 | ################################### 136 | # Static methods # 137 | ################################### 138 | 139 | class Order(BaseOrder, HMSMongoDataObject): 140 | """ 141 | Represents an Order in the context of the central office 142 | applications and services 143 | """ 144 | ################################### 145 | # Class attributes/constants # 146 | ################################### 147 | 148 | ################################### 149 | # Property-getter methods # 150 | ################################### 151 | 152 | # def _get_property_name(self) -> str: 153 | # return self._property_name 154 | 155 | ################################### 156 | # Property-setter methods # 157 | ################################### 158 | 159 | # def _set_property_name(self, value:str) -> None: 160 | # # TODO: Type- and/or value-check the value argument of the 161 | # # setter-method, unless it's deemed unnecessary. 162 | # self._property_name = value 163 | 164 | ################################### 165 | # Property-deleter methods # 166 | ################################### 167 | 168 | # def _del_property_name(self) -> None: 169 | # self._property_name = None 170 | 171 | ################################### 172 | # Instance property definitions # 173 | ################################### 174 | 175 | # property_name = property( 176 | # # TODO: Remove setter and deleter if access is not needed 177 | # _get_property_name, _set_property_name, _del_property_name, 178 | # 'Gets, sets or deletes the property_name (str) of the instance' 179 | # ) 180 | 181 | ################################### 182 | # Object initialization # 183 | ################################### 184 | 185 | # TODO: Add and document arguments if/as needed 186 | def __init__(self, 187 | customer:(BaseCustomer,), 188 | # - Arguments from HMSMongoDataObject 189 | oid:(UUID,str,None)=None, 190 | created:(datetime,str,float,int,None)=None, 191 | modified:(datetime,str,float,int,None)=None, 192 | is_active:(bool,int,None)=None, 193 | is_deleted:(bool,int,None)=None, 194 | is_dirty:(bool,int,None)=None, 195 | is_new:(bool,int,None)=None, 196 | *products 197 | ): 198 | """ 199 | Object initialization. 200 | 201 | self .............. (Order instance, required) The instance to 202 | execute against 203 | customer .......... (BaseCustomer, required) The customer that placed 204 | the order 205 | oid ............... (UUID|str, optional, defaults to None) The unique 206 | identifier of the object's state-data record in the 207 | back-end data-store 208 | created ........... (datetime|str|float|int, optional, defaults to None) 209 | The date/time that the object was created 210 | modified .......... (datetime|str|float|int, optional, defaults to None) 211 | The date/time that the object was last modified 212 | is_active ......... (bool|int, optional, defaults to None) A flag 213 | indicating that the object is active 214 | is_deleted ........ (bool|int, optional, defaults to None) A flag 215 | indicating that the object should be considered 216 | deleted (and may be in the near future) 217 | is_dirty .......... (bool|int, optional, defaults to None) A flag 218 | indicating that the object's data needs to be 219 | updated in the back-end data-store 220 | is_new ............ (bool|int, optional, defaults to None) A flag 221 | indicating that the object's data needs to be 222 | created in the back-end data-store 223 | products .......... (list or tuple of BaseProduct instances) The 224 | products that were ordered 225 | """ 226 | # - Call parent initializers if needed 227 | BaseOrder.__init__() 228 | HMSMongoDataObject.__init__(self, 229 | oid, created, modified, is_active, is_deleted, 230 | is_dirty, is_new 231 | ) 232 | # - Set default instance property-values using _del_... methods 233 | # - Set instance property-values from arguments using 234 | # _set_... methods 235 | # - Perform any other initialization needed 236 | pass # Remove this line 237 | 238 | ################################### 239 | # Object deletion # 240 | ################################### 241 | 242 | ################################### 243 | # Instance methods # 244 | ################################### 245 | 246 | # def instance_method(self, arg:str, *args, **kwargs): 247 | # """TODO: Document method 248 | # DOES_WHATEVER 249 | # 250 | # self .............. (Order instance, required) The instance to 251 | # execute against 252 | # arg ............... (str, required) The string argument 253 | # *args ............. (object*, optional) The arglist 254 | # **kwargs .......... (dict, optional) keyword-args, accepts: 255 | # - kwd_arg ........ (type, optional, defaults to SOMETHING) The SOMETHING 256 | # to apply 257 | # """ 258 | # pass 259 | 260 | def to_data_dict(self): 261 | return { 262 | # - BaseOrder-derived items 263 | # - BaseDataObject-derived items 264 | 'created':datetime.strftime( 265 | self.created, self.__class__._data_time_string 266 | ), 267 | 'is_active':self.is_active, 268 | 'is_deleted':self.is_deleted, 269 | 'modified':datetime.strftime( 270 | self.modified, self.__class__._data_time_string 271 | ), 272 | 'oid':str(self.oid), 273 | } 274 | 275 | ################################### 276 | # Overrides of built-in methods # 277 | ################################### 278 | 279 | ################################### 280 | # Class methods # 281 | ################################### 282 | 283 | ################################### 284 | # Static methods # 285 | ################################### 286 | 287 | -------------------------------------------------------------------------------- /hms-core/lib/systemd/system/testdaemon.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=testdaemon: a simple service example written in Python 3 | 4 | [Service] 5 | Type=forking 6 | ExecStart=/usr/bin/python3 /usr/local/bin/testdaemon.py 7 | ExecStop=/usr/bin/pkill -f testdaemon.py 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /hms-core/miscellany/initial-test-run.txt: -------------------------------------------------------------------------------- 1 | testMethodCoverage (__main__.testAddress) requires: 2 | * test__init__ 3 | * test_del_building_address 4 | * test_del_city 5 | * test_del_country 6 | * test_del_postal_code 7 | * test_del_region 8 | * test_del_street_address 9 | * test_get_building_address 10 | * test_get_city 11 | * test_get_country 12 | * test_get_postal_code 13 | * test_get_region 14 | * test_get_street_address 15 | * test_set_building_address 16 | * test_set_city 17 | * test_set_country 18 | * test_set_postal_code 19 | * test_set_region 20 | * test_set_street_address 21 | * teststandard_address 22 | testPropertyCoverage (__main__.testAddress) requires: 23 | * testbuilding_address 24 | * testcity 25 | * testcountry 26 | * testpostal_code 27 | * testregion 28 | * teststreet_address 29 | testMethodCoverage (__main__.testBaseArtisan) requires: 30 | * test__init__ 31 | * test_del_address 32 | * test_del_company_name 33 | * test_del_contact_email 34 | * test_del_contact_name 35 | * test_del_website 36 | * test_get_address 37 | * test_get_company_name 38 | * test_get_contact_email 39 | * test_get_contact_name 40 | * test_get_website 41 | * test_set_address 42 | * test_set_company_name 43 | * test_set_contact_email 44 | * test_set_contact_name 45 | * test_set_website 46 | testPropertyCoverage (__main__.testBaseArtisan) requires: 47 | * testaddress 48 | * testcompany_name 49 | * testcontact_email 50 | * testcontact_name 51 | * testwebsite 52 | testMethodCoverage (__main__.testBaseCustomer) requires: 53 | * test__init__ 54 | * test_del_billing_address 55 | * test_del_name 56 | * test_del_shipping_address 57 | * test_get_billing_address 58 | * test_get_name 59 | * test_get_shipping_address 60 | * test_set_billing_address 61 | * test_set_name 62 | * test_set_shipping_address 63 | testPropertyCoverage (__main__.testBaseCustomer) requires: 64 | * testbilling_address 65 | * testname 66 | * testshipping_address 67 | testMethodCoverage (__main__.testBaseOrder) requires: 68 | * test__init__ 69 | * test_del_customer 70 | * test_get_customer 71 | * test_set_customer 72 | testPropertyCoverage (__main__.testBaseOrder) requires: 73 | * testcustomer 74 | testMethodCoverage (__main__.testBaseProduct) requires: 75 | * test__init__ 76 | * test_del_available 77 | * test_del_description 78 | * test_del_dimensions 79 | * test_del_metadata 80 | * test_del_name 81 | * test_del_shipping_weight 82 | * test_del_store_available 83 | * test_del_summary 84 | * test_get_available 85 | * test_get_description 86 | * test_get_dimensions 87 | * test_get_metadata 88 | * test_get_name 89 | * test_get_shipping_weight 90 | * test_get_store_available 91 | * test_get_summary 92 | * test_set_available 93 | * test_set_description 94 | * test_set_dimensions 95 | * test_set_metadata 96 | * test_set_name 97 | * test_set_shipping_weight 98 | * test_set_store_available 99 | * test_set_summary 100 | * testremove_metadata 101 | * testset_metadata 102 | testPropertyCoverage (__main__.testBaseProduct) requires: 103 | * testavailable 104 | * testdescription 105 | * testdimensions 106 | * testmetadata 107 | * testname 108 | * testshipping_weight 109 | * teststore_available 110 | * testsummary 111 | testMethodCoverage (__main__.testHasProducts) requires: 112 | * test__init__ 113 | * test_del_products 114 | * test_get_products 115 | * test_set_products 116 | testPropertyCoverage (__main__.testHasProducts) requires: 117 | * testproducts 118 | -------------------------------------------------------------------------------- /hms-core/requirements.txt: -------------------------------------------------------------------------------- 1 | docutils==0.14 2 | lockfile==0.12.2 3 | pkg-resources==0.0.0 4 | pymongo==3.6.1 5 | PyYAML==3.13 6 | -------------------------------------------------------------------------------- /hms-core/scratch-space/HasProducts.py: -------------------------------------------------------------------------------- 1 | class HasProducts(metaclass=abc.ABCMeta): 2 | """ 3 | Provides baseline functionality, interface requirements, and 4 | type-identity for objects that can have a common products 5 | property whose membership is stored and handled in the same 6 | way. 7 | """ 8 | ################################### 9 | # Class attributes/constants # 10 | ################################### 11 | 12 | ################################### 13 | # Property-getter methods # 14 | ################################### 15 | 16 | def _get_products(self) -> (tuple,): 17 | return tuple(self._products) 18 | 19 | ################################### 20 | # Property-setter methods # 21 | ################################### 22 | 23 | def _set_products(self, value:(list, tuple)) -> None: 24 | # - Check first that the value is an iterable - list or 25 | # tuple, it doesn't really matter which, just so long 26 | # as it's a sequence-type collection of some kind. 27 | if type(value) not in (list, tuple): 28 | raise TypeError( 29 | '%s.products expects a list or tuple of BaseProduct ' 30 | 'objects, but was passed a %s instead' % 31 | (self.__class__.__name__, type(value).__name__) 32 | ) 33 | # - Start with a new, empty list 34 | new_items = [] 35 | # - Iterate over the items in value, check each one, and 36 | # append them if they're OK 37 | bad_items = [] 38 | for item in value: 39 | # - We're going to assume that all products will derive 40 | # from BaseProduct - That's why it's defined, after all 41 | if isinstance(item, BaseProduct): 42 | new_items.append(item) 43 | else: 44 | bad_items.append(item) 45 | # - If there are any bad items, then do NOT commit the 46 | # changes -- raise an error instead! 47 | if bad_items: 48 | raise TypeError( 49 | '%s.products expects a list or tuple of BaseProduct ' 50 | 'objects, but the value passed included %d items ' 51 | 'that are not of the right type: (%s)' % 52 | ( 53 | self.__class__.__name__, len(bad_items), 54 | ', '.join([str(bi) for bi in bad_items]) 55 | ) 56 | ) 57 | self._products = value 58 | 59 | ################################### 60 | # Property-deleter methods # 61 | ################################### 62 | 63 | def _del_products(self) -> None: 64 | self._products = [] 65 | 66 | ################################### 67 | # Instance property definitions # 68 | ################################### 69 | 70 | products = property( 71 | _get_products, None, None, 72 | 'Gets the products (BaseProduct) of the instance' 73 | ) 74 | 75 | ################################### 76 | # Object initialization # 77 | ################################### 78 | 79 | def __init__(self, *products): 80 | """ 81 | Object initialization. 82 | 83 | self .............. (BaseOrder instance, required) The instance to 84 | execute against 85 | products .......... (list or tuple of BaseProduct instances) The 86 | products that were ordered 87 | """ 88 | # - Call parent initializers if needed 89 | # - Set default instance property-values using _del_... methods 90 | self._del_products() 91 | # - Set instance property-values from arguments using 92 | # _set_... methods 93 | if products: 94 | self._set_products(products) 95 | # - Perform any other initialization needed 96 | 97 | ################################### 98 | # Object deletion # 99 | ################################### 100 | 101 | ################################### 102 | # Abstract methods # 103 | ################################### 104 | 105 | @abc.abstractmethod 106 | def add_product(self, product:BaseProduct) -> BaseProduct: 107 | """ 108 | Adds a product to the instance's collection of products. 109 | 110 | Returns the product added. 111 | 112 | self ....... (HasProducts instance, required) The instance to 113 | execute against 114 | product ... (BaseProduct, required) The product to add to the 115 | instance's collection of products 116 | 117 | Raises TypeError if the product specified is not a BaseProduct- 118 | derived instance 119 | 120 | May be implemented in derived classes by simply calling 121 | return HasProducts.add_product(self, product) 122 | """ 123 | # - Make sure the product passed in is a BaseProduct 124 | if not isinstance(product, BaseProduct): 125 | raise TypeError( 126 | '%s.add_product expects an instance of ' 127 | 'BaseProduct to be passed in its product ' 128 | 'argument, but "%s" (%s) was passed instead' % 129 | ( 130 | self.__class__.__name__, value, 131 | type(value).__name__ 132 | ) 133 | ) 134 | # - Append it to the internal _products list 135 | self._products.append(product) 136 | # - Return it 137 | return product 138 | 139 | @abc.abstractmethod 140 | def remove_product(self, product:BaseProduct): 141 | """ 142 | Removes a product from the instance's collection of products. 143 | 144 | Returns the product removed. 145 | 146 | self ....... (HasProducts instance, required) The instance to 147 | execute against 148 | product ... (BaseProduct, required) The product to remove from 149 | the instance's collection of products 150 | 151 | Raises TypeError if the product specified is not a BaseProduct- 152 | derived instance 153 | Raises ValueError if the product specified is not a member of the 154 | instance's products collection 155 | 156 | May be implemented in derived classes by simply calling 157 | return HasProducts.remove_product(self, product) 158 | """ 159 | # - Make sure the product passed in is a BaseProduct. 160 | # Technically this may not be necessary, since type 161 | # is enforced in add_product, but it does no harm to 162 | # re-check here... 163 | if not isinstance(product, BaseProduct): 164 | raise TypeError( 165 | '%s.add_product expects an instance of ' 166 | 'BaseProduct to be passed in its product ' 167 | 'argument, but "%s" (%s) was passed instead' % 168 | ( 169 | self.__class__.__name__, value, 170 | type(value).__name__ 171 | ) 172 | ) 173 | try: 174 | self._products.remove(product) 175 | return product 176 | except ValueError: 177 | raise ValueError( 178 | '%s.remove_product could not remove %s from its ' 179 | 'products collection because it was not a member ' 180 | 'of that collection' % 181 | (self.__class__.__name__, product) 182 | ) 183 | 184 | ################################### 185 | # Overrides of built-in methods # 186 | ################################### 187 | 188 | ################################### 189 | # Class methods # 190 | ################################### 191 | 192 | ################################### 193 | # Static methods # 194 | ################################### 195 | 196 | 197 | class BaseArtisan(HasProducts, metaclass=abc.ABCMeta): 198 | """ 199 | Provides baseline functionality, interface requirements, and 200 | type-identity for objects that can represent an Artisan in 201 | the context of the HMS system. 202 | """ 203 | 204 | def __init__(self, 205 | contact_name:str, contact_email:str, 206 | address:Address, company_name:str=None, 207 | **products 208 | ): 209 | """ 210 | Object initialization. 211 | 212 | self .............. (BaseArtisan instance, required) The instance to 213 | execute against 214 | contact_name ...... (str, required) The name of the primary contact 215 | for the Artisan that the instance represents 216 | contact_email ..... (str [email address], required) The email address 217 | of the primary contact for the Artisan that the 218 | instance represents 219 | address ........... (Address, required) The mailing/shipping address 220 | for the Artisan that the instance represents 221 | company_name ...... (str, optional, defaults to None) The company- 222 | name for the Artisan that the instance represents 223 | products .......... (BaseProduct collection) The products associated 224 | with the Artisan that the instance represents 225 | """ 226 | # - Call parent initializers if needed 227 | HasProducts.__init__(self, *products) 228 | # - Set default instance property-values using _del_... methods 229 | self._del_address() 230 | self._del_company_name() 231 | self._del_contact_email() 232 | self._del_contact_name() 233 | # This can be deleted, or just commented out. 234 | # self._del_products() 235 | # - Set instance property-values from arguments using 236 | # _set_... methods 237 | self._set_contact_name(contact_name) 238 | self._set_contact_email(contact_email) 239 | self._set_address(address) 240 | if company_name: 241 | self._set_company_name(company_name) 242 | # This also can be deleted, or just commented out. 243 | # if products: 244 | # self._set_products(products) 245 | # - Perform any other initialization needed 246 | 247 | -------------------------------------------------------------------------------- /hms-core/scratch-space/callable-object.py: -------------------------------------------------------------------------------- 1 | class callable_class: 2 | def __init__(self, some_arg, some_other_arg): 3 | self._some_arg = some_arg 4 | self._some_other_arg = some_other_arg 5 | 6 | def __call__(self, arg): 7 | print('%s("%s") called:' % (self.__class__.__name__, arg)) 8 | print('+- self._some_arg ......... %s' % (self._some_arg)) 9 | print('+- self._some_other_arg ... %s' % (self._some_other_arg)) 10 | 11 | instance1 = callable_class('instance 1', 'other arg') 12 | instance1('calling instance 1') 13 | 14 | instance2 = callable_class(instance1, True) 15 | instance2('calling instance 2') 16 | -------------------------------------------------------------------------------- /hms-core/scratch-space/config-examples/daemons-config-json-yaml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json, yaml 4 | 5 | CONFIG={ 6 | 'logging':{ 7 | 'name':'daemon-name', 8 | 'format':'%(asctime)s - %(name)s - %(levelname)s - %(message)s', 9 | 'file':{ 10 | 'logfile':'/var/log/daemon-name.log', 11 | 'level':'debug', 12 | }, 13 | 'console':{ 14 | 'level':'error', 15 | } 16 | }, 17 | } 18 | 19 | 20 | # - Convert to JSON 21 | with open('example-config.json', 'w') as fp: 22 | json.dump(CONFIG, fp, indent=4) 23 | 24 | # - Convert to YAML 25 | with open('example-config.yaml', 'w') as fp: 26 | yaml.dump(CONFIG, fp, default_flow_style=False) 27 | -------------------------------------------------------------------------------- /hms-core/scratch-space/config-examples/example-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging": { 3 | "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", 4 | "file": { 5 | "logfile": "/var/log/daemon-name.log", 6 | "level": "debug" 7 | }, 8 | "console": { 9 | "level": "error" 10 | }, 11 | "name": "daemon-name" 12 | } 13 | } -------------------------------------------------------------------------------- /hms-core/scratch-space/config-examples/example-config.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | console: 3 | level: error 4 | file: 5 | level: debug 6 | logfile: /tmp/daemon-name.log 7 | format: '%(asctime)s - %(name)s - %(levelname)8s - %(message)s' 8 | name: daemon-name 9 | daemon: 10 | pidfile: /tmp/testdaemon/pid 11 | # stdin: /var/cache/testdaemon/daemon.in 12 | # stdout: /var/cache/testdaemon/daemon.out 13 | # stderr: /var/cache/testdaemon/daemon.err 14 | 15 | -------------------------------------------------------------------------------- /hms-core/scratch-space/data-storage-demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from hms_core.data_storage import HMSMongoDataObject, DatastoreConfig 4 | 5 | class ExampleObject(HMSMongoDataObject): 6 | 7 | _data_dict_keys = ( 8 | 'name', 'description', 'cost', 'oid', 'created', 'modified', 9 | 'is_active', 'is_deleted' 10 | ) 11 | 12 | def __init__(self, name=None, description=None, cost=0, 13 | oid=None, created=None, modified=None, is_active=None, 14 | is_deleted=None, is_dirty=None, is_new=None 15 | ): 16 | HMSMongoDataObject.__init__( 17 | self, oid, created, modified, is_active, is_deleted, 18 | is_dirty, is_new 19 | ) 20 | self.name = name 21 | self.description = description 22 | self.cost = cost 23 | 24 | def __str__(self): 25 | return '<%s oid: %s>' % (self.__class__.__name__, self.oid) 26 | 27 | def matches(self, **criteria): 28 | return HMSMongoDataObject.matches(self, **criteria) 29 | 30 | def to_data_dict(self): 31 | return { 32 | # - "local" properties 33 | 'name':self.name, 34 | 'description':self.description, 35 | 'cost':self.cost, 36 | # - standard items from HMSMongoDataObject/BaseDataObject 37 | 'created':self.created.strftime(self.__class__._data_time_string), 38 | 'is_active':self.is_active, 39 | 'is_deleted':self.is_deleted, 40 | 'modified':self.modified.strftime(self.__class__._data_time_string), 41 | 'oid':str(self.oid), 42 | } 43 | 44 | @classmethod 45 | def sort(cls, objects, sorty_by): 46 | pass 47 | 48 | if __name__ == '__main__': 49 | 50 | HMSMongoDataObject.configure( 51 | DatastoreConfig(database='demo_data') 52 | ) 53 | 54 | print('Creating data-objects to demo with') 55 | names = ['Alice', 'Bob', 'Carl', 'Doug'] 56 | costs = [1, 2, 3] 57 | descriptions = [None, 'Description'] 58 | all_oids = [] 59 | for name in names: 60 | for description in descriptions: 61 | for cost in costs: 62 | item = ExampleObject( 63 | name=name, description=description, cost=cost 64 | ) 65 | item.save() 66 | -------------------------------------------------------------------------------- /hms-core/scratch-space/datastories.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | compsys={ 4 | 'Artisan':{ 5 | 'db':'file-store', 6 | 'objects':['Artisan','Product','Order','Customer'], 7 | }, 8 | 'Central Office user':{ 9 | 'db':'file-store', 10 | 'objects':['Artisan','Product','Order','Customer'], 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /hms-core/scratch-space/diamond-problem.py: -------------------------------------------------------------------------------- 1 | class Root: 2 | def method(self, arg, *args, **kwargs): 3 | print('Root.method(%s, %s, %s)' % (arg, str(args), kwargs)) 4 | 5 | class Left(Root): 6 | def method(self, arg, *args, **kwargs): 7 | print('Left.method(%s, %s, %s)' % (arg, str(args), kwargs)) 8 | 9 | class Right(Root): 10 | def method(self, arg, *args, **kwargs): 11 | print('Right.method(%s, %s, %s)' % (arg, str(args), kwargs)) 12 | 13 | class Bottom(Left, Right): 14 | pass 15 | 16 | b = Bottom() 17 | b.method('arg', 'args1', 'args2', keyword='value') 18 | # Outputs "Left.method(arg, ('args1', 'args2'), {'keyword': 'value'})" 19 | -------------------------------------------------------------------------------- /hms-core/scratch-space/dict-args-example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Shows how passing keyword-like values, or a dict using a **keywords approach, still works 4 | """ 5 | 6 | def example( 7 | street_address:(str,), city:(str,), 8 | building_address:(str,None)=None, region:(str,None)=None, 9 | postal_code:(str,None)=None, country:(str,None)=None 10 | ): 11 | print('street_address .............. %s' 12 | % street_address) 13 | print('building_address ............ %s' 14 | % building_address) 15 | print('city, region, postal_code ... %s, %s, %s' 16 | % (city, region, postal_code)) 17 | print('country ..................... %s' 18 | % country) 19 | 20 | example( 21 | street_address='1234 Main Street', city='Some Town', 22 | region='OK', postal_code='00000' 23 | ) 24 | 25 | my_address = { 26 | 'street_address':'456 South Broadway', 27 | 'building_address':'Suite 234', 28 | 'city':'Nowhereville', 29 | 'region':'WH', 30 | 'postal_code':'B0RT0N', 31 | 'country':'Imaginaria' 32 | } 33 | 34 | example(**my_address) 35 | -------------------------------------------------------------------------------- /hms-core/scratch-space/email-checks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | 5 | from email.utils import parseaddr 6 | 7 | EMAIL_CHECK = re.compile( 8 | r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)' 9 | ) 10 | 11 | for value in [ 12 | 'brian.allbee@gmail.com', 13 | 'brian.allbee+hosep-test@gmail.com', 14 | 'flobnar' 15 | ]: 16 | try: 17 | check_value = parseaddr(value)[1] 18 | valid = (check_value != '') 19 | if valid: 20 | remnant = EMAIL_CHECK.sub('', check_value) 21 | print('remnant ... %s' % remnant) 22 | if not remnant: 23 | print('%s was valid' % value) 24 | else: 25 | raise ValueError( 26 | '%s was invalid after regex (%s [%s])' % 27 | (value, check_value, remnant) 28 | ) 29 | else: 30 | raise ValueError( 31 | '%s was invalid after parseaddr (%s)' % 32 | (value, check_value) 33 | ) 34 | except Exception as error: 35 | print(error) 36 | -------------------------------------------------------------------------------- /hms-core/scratch-space/list-reference.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example showing that objects are accessed by reference in collections 4 | """ 5 | 6 | # - Create a class to demonstrate with 7 | class Example: 8 | pass 9 | 10 | # - Create a list of instances of the class 11 | example_list = [ 12 | Example(), Example(), Example(), Example() 13 | ] 14 | 15 | print('Items in the original list (at %s):' % hex(id(example_list))) 16 | for item in example_list: 17 | print(item) 18 | print() 19 | 20 | # Items in the original list (at 0x7f9cd9ed6a48): 21 | # <__main__.Example object at 0x7f9cd9eed550> 22 | # <__main__.Example object at 0x7f9cd9eed5c0> 23 | # <__main__.Example object at 0x7f9cd9eed5f8> 24 | # <__main__.Example object at 0x7f9cd9eed630> 25 | 26 | new_list = list(example_list) 27 | print('Items in the new list (at %s):' % hex(id(new_list))) 28 | for item in new_list: 29 | print(item) 30 | print() 31 | 32 | # Items in the new list (at 0x7f9cd89dca88): 33 | # <__main__.Example object at 0x7f9cd9eed550> 34 | # <__main__.Example object at 0x7f9cd9eed5c0> 35 | # <__main__.Example object at 0x7f9cd9eed5f8> 36 | # <__main__.Example object at 0x7f9cd9eed630> 37 | 38 | new_tuple = tuple(example_list) 39 | print('Items in the new tuple (at %s):' % hex(id(new_tuple))) 40 | for item in new_tuple: 41 | print(item) 42 | print() 43 | 44 | # Items in the new tuple (at 0x7f9cd9edd4a8): 45 | # <__main__.Example object at 0x7f9cd9eed550> 46 | # <__main__.Example object at 0x7f9cd9eed5c0> 47 | # <__main__.Example object at 0x7f9cd9eed5f8> 48 | # <__main__.Example object at 0x7f9cd9eed630> 49 | -------------------------------------------------------------------------------- /hms-core/scratch-space/testdaemond: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ~/py_envs/hms/core/bin/python ~/git/hms_sys/hms-core/src/hms_core/daemons.py 4 | -------------------------------------------------------------------------------- /hms-core/scratch-space/url-checks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | 5 | URL_CHECK = re.compile( 6 | r'(^https?://[A-Za-z0-9][-_A-Za-z0-9]*\.[A-Za-z0-9][-_A-Za-z0-9\.]*$)' 7 | ) 8 | 9 | for value in [ 10 | 'https://cxos.io', 11 | 'http://www.google.com', 12 | 'https://', 13 | 'ook.com', 14 | ]: 15 | try: 16 | remnant = URL_CHECK.sub('', value) 17 | print('remnant ... %s' % remnant) 18 | if not remnant: 19 | print('%s was valid' % value) 20 | else: 21 | raise ValueError( 22 | '%s was invalid after regex (%s)' % 23 | (value, remnant) 24 | ) 25 | except Exception as error: 26 | print(error) 27 | print() 28 | -------------------------------------------------------------------------------- /hms-core/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | sys.path.append('../standards') 5 | sys.path.append('tests/test_hms_core') 6 | 7 | from setuptools import setup 8 | 9 | # The actual setup function call: 10 | setup( 11 | name='HMS-Core', 12 | version='0.1.dev0', 13 | author='Brian D. Allbee', 14 | description='', 15 | package_dir={ 16 | '':'src', 17 | # ... 18 | }, 19 | # Can also be automatically generated using 20 | # setuptools.find_packages... 21 | packages=[ 22 | 'hms_core', 23 | # ... 24 | ], 25 | package_data={ 26 | # 'hms_core':[ 27 | # 'filename.ext', 28 | # # ... 29 | # ] 30 | }, 31 | entry_points={ 32 | # 'console_scripts':[ 33 | # 'executable_name = namespace.path:function', 34 | # # ... 35 | # ], 36 | }, 37 | test_suite='tests.test_hms_core', 38 | ) 39 | -------------------------------------------------------------------------------- /hms-core/src/hms_core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Package-header for the hms_core namespace. 4 | Provides classes and functionality for SOME_PURPOSE 5 | """ 6 | 7 | ####################################### 8 | # Create an "__all__" list to support # 9 | # "from module import member" use # 10 | ####################################### 11 | 12 | __all__ = [ 13 | # Constants 14 | # Exceptions 15 | # Functions 16 | # ABC "interface" classes 17 | # ABC abstract classes 18 | # Concrete classes 19 | # Child packages and modules 20 | 'business_objects', 21 | 'co_objects', 22 | 'daemons', 23 | 'data_objects', 24 | 'data_storage', 25 | ] 26 | 27 | ####################################### 28 | # Module metadata/dunder-names # 29 | ####################################### 30 | 31 | __author__ = 'Brian D. Allbee' 32 | __copyright__ = 'Copyright 2018, all rights reserved' 33 | __status__ = 'Development' 34 | 35 | ####################################### 36 | # Standard library imports needed # 37 | ####################################### 38 | 39 | import abc 40 | import re 41 | 42 | from email.utils import parseaddr 43 | 44 | ####################################### 45 | # Third-party imports needed # 46 | ####################################### 47 | 48 | ####################################### 49 | # Local imports needed # 50 | ####################################### 51 | 52 | ####################################### 53 | # Initialization needed before member # 54 | # definition can take place # 55 | ####################################### 56 | 57 | ####################################### 58 | # Module-level Constants # 59 | ####################################### 60 | 61 | ####################################### 62 | # Custom Exceptions # 63 | ####################################### 64 | 65 | ####################################### 66 | # Module functions # 67 | ####################################### 68 | 69 | ####################################### 70 | # ABC "interface" classes # 71 | ####################################### 72 | 73 | ####################################### 74 | # Concrete classes # 75 | ####################################### 76 | 77 | ####################################### 78 | # Initialization needed after member # 79 | # definition is complete # 80 | ####################################### 81 | 82 | ####################################### 83 | # Imports needed after member # 84 | # definition (to resolve circular # 85 | # dependencies - avoid if at all # 86 | # possible # 87 | ####################################### 88 | 89 | ####################################### 90 | # Code to execute if the module is # 91 | # called directly # 92 | ####################################### 93 | 94 | if __name__ == '__main__': 95 | pass 96 | 97 | -------------------------------------------------------------------------------- /hms-core/src/hms_core/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Software-Engineering-with-Python/f12cd71d475c5286947106174d94673a699bec1f/hms-core/src/hms_core/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /hms-core/tests/run_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Executes the project's test-suite 4 | """ 5 | 6 | ####################################### 7 | # Standard library imports needed # 8 | ####################################### 9 | 10 | import os 11 | import sys 12 | import time 13 | import unittest 14 | 15 | ####################################### 16 | # Third-party imports needed # 17 | ####################################### 18 | 19 | ####################################### 20 | # Local imports needed # 21 | ####################################### 22 | 23 | from idic.unit_testing import * 24 | from test_hms_core import LocalSuite 25 | 26 | ####################################### 27 | # Module functions # 28 | ####################################### 29 | 30 | def main(): 31 | results = unittest.TestResult() 32 | testStartTime = time.time() 33 | LocalSuite.run(results) 34 | results.runTime = time.time() - testStartTime 35 | PrintTestResults(results) 36 | if not results.errors and not results.failures: 37 | SaveTestReport(results, 'hms_core', 38 | 'hms_core.test-results') 39 | sys.exit(0) 40 | else: 41 | sys.exit(1) 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Defines unit-tests for the module at hms_core. 5 | """ 6 | 7 | ####################################### 8 | # Any needed from __future__ imports # 9 | # Create an "__all__" list to support # 10 | # "from module import member" use # 11 | ####################################### 12 | 13 | __all__ = [ 14 | # Test-case classes 15 | # Child test-modules 16 | 'test_business_objects', 17 | 'test_co_objects', 18 | 'test_data_objects', 19 | 'test_data_storage', 20 | ] 21 | 22 | ####################################### 23 | # Module metadata/dunder-names # 24 | ####################################### 25 | 26 | __author__ = 'Brian D. Allbee' 27 | __copyright__ = 'Copyright 2018, all rights reserved' 28 | __status__ = 'Development' 29 | 30 | ####################################### 31 | # Standard library imports needed # 32 | ####################################### 33 | 34 | import os 35 | import sys 36 | import unittest 37 | 38 | ####################################### 39 | # Third-party imports needed # 40 | ####################################### 41 | 42 | ####################################### 43 | # Local imports needed # 44 | ####################################### 45 | 46 | from idic.unit_testing import * 47 | 48 | ####################################### 49 | # Initialization needed before member # 50 | # definition can take place # 51 | ####################################### 52 | 53 | ####################################### 54 | # Module-level Constants # 55 | ####################################### 56 | 57 | LocalSuite = unittest.TestSuite() 58 | 59 | ####################################### 60 | # Import the module being tested # 61 | ####################################### 62 | 63 | import hms_core as hms_core 64 | from hms_core import * 65 | 66 | ####################################### 67 | # Constants for test-methods # 68 | ####################################### 69 | 70 | ####################################### 71 | # Code-coverage test-case and # 72 | # decorator-methods # 73 | ####################################### 74 | 75 | class testhms_coreCodeCoverage(ModuleCoverageTest): 76 | # - Class constants that point to the namespace and module 77 | # being tested 78 | _testNamespace = 'hms_core' 79 | _testModule = hms_core 80 | 81 | LocalSuite.addTests( 82 | unittest.TestLoader().loadTestsFromTestCase( 83 | testhms_coreCodeCoverage 84 | ) 85 | ) 86 | 87 | ####################################### 88 | # Test-cases in the module # 89 | ####################################### 90 | 91 | ####################################### 92 | # Child-module test-cases to execute # 93 | ####################################### 94 | 95 | import test_business_objects 96 | LocalSuite.addTests(test_business_objects.LocalSuite._tests) 97 | 98 | import test_co_objects 99 | LocalSuite.addTests(test_co_objects.LocalSuite._tests) 100 | 101 | import test_data_objects 102 | LocalSuite.addTests(test_data_objects.LocalSuite._tests) 103 | 104 | import test_data_storage 105 | LocalSuite.addTests(test_data_storage.LocalSuite._tests) 106 | 107 | # import child_module 108 | # LocalSuite.addTests(child_module.LocalSuite._tests) 109 | 110 | ####################################### 111 | # Imports to resolve circular # 112 | # dependencies. Avoid if possible. # 113 | ####################################### 114 | 115 | ####################################### 116 | # Initialization that needs to # 117 | # happen after member definition. # 118 | ####################################### 119 | 120 | ####################################### 121 | # Code to execute if file is called # 122 | # or run directly. # 123 | ####################################### 124 | 125 | if __name__ == '__main__': 126 | import time 127 | results = unittest.TestResult() 128 | testStartTime = time.time() 129 | LocalSuite.run(results) 130 | results.runTime = time.time() - testStartTime 131 | PrintTestResults(results) 132 | if not results.errors and not results.failures: 133 | SaveTestReport(results, 'hms_core', 134 | 'hms_core.test-results') 135 | -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Software-Engineering-with-Python/f12cd71d475c5286947106174d94673a699bec1f/hms-core/tests/test_hms_core/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/hms_core.business_objects.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_core.business_objects 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 118 7 | Number of tests skipped ... 0 8 | Tests ran in .......... 0.847 seconds 9 | ################################################################################ -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/hms_core.co_objects.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_core.co_objects 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 55 7 | Number of tests skipped ... 2 8 | Tests ran in .......... 0.032 seconds 9 | #------------------------------------------------------------------------------# 10 | List of skipped tests and the reasons they were skipped: 11 | testsort (__main__.testArtisan) 12 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 13 | testsort (__main__.testProduct) 14 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 15 | ################################################################################ -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/hms_core.data_objects.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_core.data_objects 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 33 7 | Number of tests skipped ... 0 8 | Tests ran in .......... 0.003 seconds 9 | ################################################################################ -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/hms_core.data_storage.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_core.data_storage 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 47 7 | Number of tests skipped ... 5 8 | Tests ran in .......... 0.411 seconds 9 | #------------------------------------------------------------------------------# 10 | List of skipped tests and the reasons they were skipped: 11 | test_del_collection (__main__.testHMSMongoDataObject) 12 | - Tested in test_get_collection 13 | test_del_connection (__main__.testHMSMongoDataObject) 14 | - Tested in test_get_connection 15 | test_del_database (__main__.testHMSMongoDataObject) 16 | - Tested in test_get_database 17 | test_get_configuration (__main__.testHMSMongoDataObject) 18 | - The fact that the configuration works in setUp is sufficient 19 | testconfigure (__main__.testHMSMongoDataObject) 20 | - The fact that the configuration works in setUp is sufficient 21 | ################################################################################ -------------------------------------------------------------------------------- /hms-core/tests/test_hms_core/hms_core.test-results: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | Unit-test Results: hms_core 4 | #------------------------------------------------------------------------------# 5 | Tests were SUCCESSFUL 6 | Number of tests run ....... 256 7 | Number of tests skipped ... 9 8 | Tests ran in .......... 1.332 seconds 9 | #------------------------------------------------------------------------------# 10 | List of skipped tests and the reasons they were skipped: 11 | testfrom_dict (test_business_objects.testAddress) 12 | - This was tested elsewhere by accident (see test_co_objects), and can probably be safely skipped for now. 13 | testto_dict (test_business_objects.testAddress) 14 | - This was tested elsewhere by accident (see test_co_objects), and can probably be safely skipped for now. 15 | testsort (test_co_objects.testArtisan) 16 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 17 | testsort (test_co_objects.testProduct) 18 | - Sort will be implemented once there's a need for it, and tested as part of that implementation 19 | test_del_collection (test_data_storage.testHMSMongoDataObject) 20 | - Tested in test_get_collection 21 | test_del_connection (test_data_storage.testHMSMongoDataObject) 22 | - Tested in test_get_connection 23 | test_del_database (test_data_storage.testHMSMongoDataObject) 24 | - Tested in test_get_database 25 | test_get_configuration (test_data_storage.testHMSMongoDataObject) 26 | - The fact that the configuration works in setUp is sufficient 27 | testconfigure (test_data_storage.testHMSMongoDataObject) 28 | - The fact that the configuration works in setUp is sufficient 29 | ################################################################################ -------------------------------------------------------------------------------- /hms-core/usr/local/bin/testdaemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # - Import the service-class 4 | from some_package import testdaemon 5 | 6 | # - The location of the config-file 7 | config_file = '/path/to/config.yaml' 8 | # - Create an instance of the service class 9 | d = testdaemon(config_file) 10 | # - Start it. 11 | d.start() 12 | -------------------------------------------------------------------------------- /hms-core/var/cache/hms/core/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms-core 2 | -------------------------------------------------------------------------------- /hms-core/var/cache/hms/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms applications and services 2 | -------------------------------------------------------------------------------- /hms-gateway.geany: -------------------------------------------------------------------------------- 1 | [editor] 2 | line_wrapping=false 3 | line_break_column=72 4 | auto_continue_multiline=true 5 | 6 | [file_prefs] 7 | final_new_line=true 8 | ensure_convert_new_lines=false 9 | strip_trailing_spaces=false 10 | replace_tabs=false 11 | 12 | [indentation] 13 | indent_width=4 14 | indent_type=0 15 | indent_hard_tab_width=8 16 | detect_indent=true 17 | detect_indent_width=false 18 | indent_mode=2 19 | 20 | [project] 21 | name=HMS Artisan Gateway 22 | base_path=/home/ballbee/git/hms_sys/hms-gateway 23 | description= 24 | file_patterns= 25 | 26 | [long line marker] 27 | long_line_behaviour=1 28 | long_line_column=72 29 | 30 | [files] 31 | current_page=14 32 | FILE_NAME_0=3994;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-abstract.py;0;4 33 | FILE_NAME_1=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-concrete.py;0;4 34 | FILE_NAME_2=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-interface.py;0;4 35 | FILE_NAME_3=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fmodule.py;0;4 36 | FILE_NAME_4=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fpackage__init__.py;0;4 37 | FILE_NAME_5=453;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2FREADME.md;0;4 38 | FILE_NAME_6=81;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Frequirements.txt;0;4 39 | FILE_NAME_7=533;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fsrc%2Fhms_gateway%2F__init__.py;0;4 40 | FILE_NAME_8=566;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Fpy_daemon_example.py;0;4 41 | FILE_NAME_9=540;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Fpy_daemon_func.py;0;4 42 | FILE_NAME_10=0;Conf;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Fconfiguration-examples%2Fmyservice.ini;0;4 43 | FILE_NAME_11=297;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Fconfiguration-examples%2Fini_config.py;0;4 44 | FILE_NAME_12=2;JSON;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Fconfiguration-examples%2Fmyservice.json;0;4 45 | FILE_NAME_13=76;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Fconfiguration-examples%2Fmyservice.yaml;0;4 46 | FILE_NAME_14=1812;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fhms-gateway%2Fscratch-space%2Flogging-examples%2Flogging-example.py;0;4 47 | 48 | [VTE] 49 | last_dir=/home/ballbee/git/hms_sys/hms-gateway 50 | 51 | [prjorg] 52 | source_patterns=*.c;*.C;*.cpp;*.cxx;*.c++;*.cc;*.m; 53 | header_patterns=*.h;*.H;*.hpp;*.hxx;*.h++;*.hh; 54 | ignored_dirs_patterns=.*;CVS; 55 | ignored_file_patterns=*.o;*.obj;*.a;*.lib;*.so;*.dll;*.lo;*.la;*.class;*.jar;*.pyc;*.mo;*.gmo; 56 | generate_tag_prefs=0 57 | external_dirs= 58 | 59 | [build-menu] 60 | EX_00_LB=_Execute 61 | EX_00_CM=PYTHONPATH=~/git/hms_sys/hms-gateway/src:~/git/hms_sys/hms-core/src:~/py_envs/hms/gateway/lib/python3.5/site-packages:~/py_envs/hms/core/lib/python3.5/site-packages ~/py_envs/hms/core/bin/python "%f" 62 | EX_00_WD= 63 | -------------------------------------------------------------------------------- /hms-gateway/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the HMS Artisan Gateway (hms-gateway) project 2 | 3 | main: test setup 4 | # Doesn't (yet) do anything other than running the test and 5 | # setup targets 6 | 7 | setup: 8 | # Calls the main setup.py to build a source-distribution 9 | # python setup.py sdist 10 | 11 | test: 12 | # Executes the unit-tests for the package, allowing the build- 13 | # process to die and stop the build if a test fails 14 | -------------------------------------------------------------------------------- /hms-gateway/README.md: -------------------------------------------------------------------------------- 1 | # HMS Artisan Gateway 2 | 3 | Provides an executable service that the Artisan Application and Central 4 | Office Application use to send information back and forth between the 5 | artisans and the central office. 6 | 7 | ## Python Virtual Environment 8 | 9 | **Create:** `python3 -m venv ~/py_envs/hms/gateway` 10 | **Activate:** `source ~/py_envs/hms/gateway/bin/activate` 11 | **Upgrade pip:** `pip install --upgrade pip` 12 | 13 | ## Build process 14 | 15 | `python3 setup.py sdist` (source-distribution) 16 | -------------------------------------------------------------------------------- /hms-gateway/etc/hms/hms_gateway.conf: -------------------------------------------------------------------------------- 1 | # A placeholder for the config-file for hms_gateway 2 | -------------------------------------------------------------------------------- /hms-gateway/requirements.txt: -------------------------------------------------------------------------------- 1 | docutils==0.14 2 | lockfile==0.12.2 3 | pkg-resources==0.0.0 4 | pymongo==3.6.1 5 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/configuration-examples/ini_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import configparser 4 | 5 | config = configparser.ConfigParser() 6 | config.read('myservice.ini') 7 | 8 | for section in config: 9 | # - Show each section's name 10 | print(('-- %s ' % section).ljust(80, '-')) 11 | section = config[section] 12 | # - Show each configured value in the section 13 | for key in section: 14 | value = section.get(key) 15 | print( 16 | (' + %s ' % key).ljust(24, '.') 17 | + ' (%s) %s' % ( 18 | type(value).__name__, value 19 | ) 20 | ) 21 | print() 22 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/configuration-examples/myservice.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | # This section handles settings-values that are available in other 3 | # sections. 4 | # - The minimum log-level that's in play 5 | log_level: INFO 6 | queue_type: rabbit 7 | queue_check: 5 8 | 9 | [console_log] 10 | # Settings for logging of messages to the console 11 | # - Message-types to log to a console 12 | capture: INFO, WARNING 13 | 14 | [file_log] 15 | # Settings for file-logging 16 | log_file: /var/log/myservice/activity.log 17 | 18 | [rabbit_config] 19 | # Configuration for the RabbitMQ server, if queue_type is "rabbit" 20 | server: 10.1.10.1 21 | port: 5672 22 | queue_name: my-queue 23 | user: username 24 | password: password 25 | 26 | [sqs_config] 27 | # Configuration for SQS access, if queue_type is "sqs" 28 | queue_url: the URL of the SQS queue 29 | user: username 30 | password: password 31 | auto_delete: true 32 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/configuration-examples/myservice.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging": { 3 | "log_level": "INFO", 4 | "console_capture": ["INFO","WARNING"], 5 | "log_file": "/var/log/myservice/activity.log" 6 | }, 7 | "queue": { 8 | "queue_type": "rabbit", 9 | "queue_check": 5, 10 | "server": "10.1.10.1", 11 | "port": 5672, 12 | "queue_name": "my-queue", 13 | "user": "username", 14 | "password": "password" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/configuration-examples/myservice.yaml: -------------------------------------------------------------------------------- 1 | # Logging configuration 2 | logging: 3 | console_capture: 4 | - INFO 5 | - WARNING 6 | log_file: /var/log/myservice/activity.log 7 | log_level: INFO 8 | # Queue configuration 9 | queue: 10 | queue_type: rabbit 11 | # Credentials 12 | user: username 13 | password: password 14 | # Network 15 | server: 10.1.10.1 16 | port: 5672 17 | # Queue settings 18 | queue_name: my-queue 19 | queue_check: 5 20 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/logging-examples/example.log: -------------------------------------------------------------------------------- 1 | 2018-07-08 11:00:49,791 - logging-example - INFO - Calling with no arguments 2 | 2018-07-08 11:00:49,791 - logging-example - INFO - some_function((), {}) called 3 | 2018-07-08 11:00:49,791 - logging-example - WARNING - some_function was called with no arguments 4 | 2018-07-08 11:00:49,791 - logging-example - INFO - some_function complete 5 | 2018-07-08 11:00:49,791 - logging-example - INFO - Calling with x and y values 6 | 2018-07-08 11:00:49,791 - logging-example - INFO - some_function((1, 2), {}) called 7 | 2018-07-08 11:00:49,791 - logging-example - DEBUG - *args exists: (1, 2) 8 | 2018-07-08 11:00:49,792 - logging-example - DEBUG - x = 1, y = 2 9 | 2018-07-08 11:00:49,792 - logging-example - INFO - Calling with a bad y-value 10 | 2018-07-08 11:00:49,792 - logging-example - INFO - some_function((1, 0), {}) called 11 | 2018-07-08 11:00:49,792 - logging-example - DEBUG - *args exists: (1, 0) 12 | 2018-07-08 11:00:49,792 - logging-example - DEBUG - x = 1, y = 0 13 | 2018-07-08 11:00:49,792 - logging-example - ERROR - ZeroDivisionError in some_function: division by zero 14 | 2018-07-08 11:00:49,792 - logging-example - INFO - some_function complete 15 | 16 | 17 | 18 | 19 | 2018-07-08 11:01:52,076 - logging-example - ERROR - ZeroDivisionError in some_function: division by zero 20 | 21 | 22 | 23 | 24 | 2018-07-08 11:02:23,074 - logging-example - CRITICAL - This is a CRITICAL-level message 25 | 2018-07-08 11:02:23,075 - logging-example - ERROR - This is an ERROR-level message 26 | 2018-07-08 11:02:23,075 - logging-example - INFO - This is an INFO-level message 27 | 2018-07-08 11:02:23,075 - logging-example - WARNING - This is a WARNING-level message 28 | 2018-07-08 11:02:23,075 - logging-example - INFO - Calling with no arguments 29 | 2018-07-08 11:02:23,075 - logging-example - INFO - some_function((), {}) called 30 | 2018-07-08 11:02:23,075 - logging-example - WARNING - some_function was called with no arguments 31 | 2018-07-08 11:02:23,076 - logging-example - INFO - some_function complete 32 | 2018-07-08 11:02:23,076 - logging-example - INFO - Calling with x and y values 33 | 2018-07-08 11:02:23,076 - logging-example - INFO - some_function((1, 2), {}) called 34 | 2018-07-08 11:02:23,076 - logging-example - INFO - Calling with a bad y-value 35 | 2018-07-08 11:02:23,076 - logging-example - INFO - some_function((1, 0), {}) called 36 | 2018-07-08 11:02:23,076 - logging-example - ERROR - ZeroDivisionError in some_function: division by zero 37 | 2018-07-08 11:02:23,076 - logging-example - INFO - some_function complete 38 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/logging-examples/logging-example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | 5 | # - Define a format for log-output 6 | formatter = logging.Formatter( 7 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 8 | ) 9 | # - Get a logger. Once defned anywhere, loggers (with all their 10 | # settings and attached formats and handlers) can be retrieved 11 | # elsewhere by getting a logger instance using the same name. 12 | logger = logging.getLogger('logging-example') 13 | logger.setLevel(logging.DEBUG) 14 | # - Create a file-handler to write log-messages to a file 15 | file_handler = logging.FileHandler('example.log') 16 | file_handler.setLevel(logging.INFO) 17 | file_handler.setFormatter(formatter) 18 | # - Attach handler to logger 19 | logger.addHandler(file_handler) 20 | 21 | # - Log some messages to show that it works: 22 | logger.critical('This is a CRITICAL-level message') 23 | logger.debug('This is a DEBUG-level message') 24 | logger.error('This is an ERROR-level message') 25 | logger.info('This is an INFO-level message') 26 | logger.warn('This is a WARNING-level message') 27 | 28 | 29 | def some_function(*args, **kwargs): 30 | logger.info('some_function(%s, %s) called' % (str(args), str(kwargs))) 31 | if not args and not kwargs: 32 | logger.warn( 33 | 'some_function was called with no arguments' 34 | ) 35 | elif args: 36 | logger.debug('*args exists: %s' % (str(args))) 37 | try: 38 | x, y = args[0:2] 39 | logger.debug('x = %s, y = %s' % (x, y)) 40 | return x / y 41 | except ValueError as error: 42 | logger.error( 43 | '%s: Could not get x and y values from ' 44 | 'args %s' % 45 | (error.__class__.__name__, str(args)) 46 | ) 47 | except Exception as error: 48 | logger.error( 49 | '%s in some_function: %s' % 50 | (error.__class__.__name__, error) 51 | ) 52 | logger.info('some_function complete') 53 | 54 | logger.info('Calling with no arguments') 55 | some_function() 56 | logger.info('Calling with x and y values') 57 | some_function(1,2) 58 | logger.info('Calling with a bad y-value') 59 | some_function(1,0) 60 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/py_daemon_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A bare-bones daemon implementation. 4 | """ 5 | 6 | import syslog 7 | 8 | from daemon import DaemonContext 9 | from time import sleep 10 | 11 | def main_program(): 12 | iterations = 0 13 | syslog.syslog('Starting %s' % __file__) 14 | while True: 15 | # TODO: Perform whatever request-acquisition and response- 16 | # generation is needed here... 17 | syslog.syslog('Event Loop (%d)' % iterations) 18 | sleep(10) 19 | iterations += 1 20 | syslog.syslog('Exiting %s' % __file__) 21 | 22 | if __name__ == '__main__': 23 | with DaemonContext(): 24 | main_program() 25 | -------------------------------------------------------------------------------- /hms-gateway/scratch-space/py_daemon_func.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A simple daemon-like function that can be started from the command-line. 4 | """ 5 | 6 | import syslog 7 | 8 | from time import sleep 9 | 10 | def main_program(): 11 | iterations = 0 12 | syslog.syslog('Starting %s' % __file__) 13 | while True: 14 | # TODO: Perform whatever request-acquisition and response- 15 | # generation is needed here... 16 | syslog.syslog('Event Loop (%d)' % iterations) 17 | sleep(10) 18 | iterations += 1 19 | syslog.syslog('Exiting %s' % __file__) 20 | 21 | if __name__ == '__main__': 22 | main_program() 23 | -------------------------------------------------------------------------------- /hms-gateway/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | # The actual setup function call: 6 | setup( 7 | name='HMS-Artisan-Gateway', 8 | version='0.1.dev0', 9 | author='Brian D. Allbee', 10 | description='', 11 | package_dir={ 12 | 'hms_gateway':'src', 13 | # ... 14 | }, 15 | # Can also be automatically generated using 16 | # setuptools.find_packages... 17 | packages=[ 18 | 'hms_gateway', 19 | # ... 20 | ], 21 | package_data={ 22 | # 'hms_gateway':[ 23 | # 'filename.ext', 24 | # # ... 25 | # ] 26 | }, 27 | entry_points={ 28 | # 'console_scripts':[ 29 | # 'executable_name = namespace.path:function', 30 | # # ... 31 | # ], 32 | }, 33 | ) 34 | -------------------------------------------------------------------------------- /hms-gateway/src/hms_gateway/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | TODO: Document the package. 4 | Package-header for the hms_gateway namespace. 5 | Provides classes and functionality for SOME_PURPOSE 6 | """ 7 | 8 | ####################################### 9 | # Any needed from __future__ imports # 10 | # Create an "__all__" list to support # 11 | # "from module import member" use # 12 | ####################################### 13 | 14 | __all__ = [ 15 | # Constants 16 | # Exceptions 17 | # Functions 18 | # ABC "interface" classes 19 | # ABC abstract classes 20 | # Concrete classes 21 | # Child packages and modules 22 | ] 23 | 24 | ####################################### 25 | # Module metadata/dunder-names # 26 | ####################################### 27 | 28 | __author__ = 'Brian D. Allbee' 29 | __copyright__ = 'Copyright 2018, all rights reserved' 30 | __status__ = 'Development' 31 | 32 | ####################################### 33 | # Standard library imports needed # 34 | ####################################### 35 | 36 | ####################################### 37 | # Third-party imports needed # 38 | ####################################### 39 | 40 | # Uncomment this if there are abstract classes or "interfaces" 41 | # defined in the module... 42 | # import abc 43 | 44 | ####################################### 45 | # Local imports needed # 46 | ####################################### 47 | 48 | ####################################### 49 | # Initialization needed before member # 50 | # definition can take place # 51 | ####################################### 52 | 53 | ####################################### 54 | # Module-level Constants # 55 | ####################################### 56 | 57 | ####################################### 58 | # Custom Exceptions # 59 | ####################################### 60 | 61 | ####################################### 62 | # Module functions # 63 | ####################################### 64 | 65 | ####################################### 66 | # ABC "interface" classes # 67 | ####################################### 68 | 69 | ####################################### 70 | # Abstract classes # 71 | ####################################### 72 | 73 | ####################################### 74 | # Concrete classes # 75 | ####################################### 76 | 77 | ####################################### 78 | # Initialization needed after member # 79 | # definition is complete # 80 | ####################################### 81 | 82 | ####################################### 83 | # Imports needed after member # 84 | # definition (to resolve circular # 85 | # dependencies - avoid if at all # 86 | # possible # 87 | ####################################### 88 | 89 | ####################################### 90 | # Code to execute if the module is # 91 | # called directly # 92 | ####################################### 93 | 94 | if __name__ == '__main__': 95 | pass 96 | -------------------------------------------------------------------------------- /hms-gateway/var/cache/hms/gateway/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms-gateway 2 | -------------------------------------------------------------------------------- /hms-gateway/var/cache/hms/placeholder.txt: -------------------------------------------------------------------------------- 1 | # The cache directory for hms applications and services 2 | -------------------------------------------------------------------------------- /hms_sys.geany: -------------------------------------------------------------------------------- 1 | [editor] 2 | line_wrapping=false 3 | line_break_column=72 4 | auto_continue_multiline=true 5 | 6 | [file_prefs] 7 | final_new_line=true 8 | ensure_convert_new_lines=false 9 | strip_trailing_spaces=false 10 | replace_tabs=false 11 | 12 | [indentation] 13 | indent_width=4 14 | indent_type=0 15 | indent_hard_tab_width=8 16 | detect_indent=true 17 | detect_indent_width=false 18 | indent_mode=2 19 | 20 | [project] 21 | name=HMS System (Overall) 22 | base_path=/home/ballbee/git/hms_sys 23 | description= 24 | 25 | [long line marker] 26 | long_line_behaviour=1 27 | long_line_column=72 28 | 29 | [files] 30 | current_page=10 31 | FILE_NAME_0=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-abstract.py;0;4 32 | FILE_NAME_1=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-concrete.py;0;4 33 | FILE_NAME_2=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fclass-interface.py;0;4 34 | FILE_NAME_3=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fmodule.py;0;4 35 | FILE_NAME_4=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Fpackage__init__.py;0;4 36 | FILE_NAME_5=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fcode-templates%2Ftest-module.py;0;4 37 | FILE_NAME_6=0;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2F.gitignore;0;4 38 | FILE_NAME_7=0;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2FREADME.md;0;4 39 | FILE_NAME_8=0;None;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Ftree.txt;0;4 40 | FILE_NAME_9=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fidic%2F__init__.py;0;4 41 | FILE_NAME_10=0;Python;0;EUTF-8;0;1;0;%2Fhome%2Fballbee%2Fgit%2Fhms_sys%2Fidic%2Funit_testing.py;0;4 42 | 43 | [VTE] 44 | last_dir=/home/ballbee/Dropbox/projects (1)/vehicle_logbook/vlogbook 45 | 46 | [prjorg] 47 | source_patterns=*.c;*.C;*.cpp;*.cxx;*.c++;*.cc;*.m; 48 | header_patterns=*.h;*.H;*.hpp;*.hxx;*.h++;*.hh; 49 | ignored_dirs_patterns=.*;CVS; 50 | ignored_file_patterns=*.o;*.obj;*.a;*.lib;*.so;*.dll;*.lo;*.la;*.class;*.jar;*.pyc;*.mo;*.gmo; 51 | generate_tag_prefs=0 52 | external_dirs= 53 | -------------------------------------------------------------------------------- /scratch-space/.coverage: -------------------------------------------------------------------------------- 1 | !coverage.py: This is a private format, don't read it directly!{"lines":{"/home/ballbee/git/hms_sys/scratch-space/test_me1.py":[35,34,3,5,7,8,10,11,12,16,17,19,20,21,25,26,28,29,30]}} -------------------------------------------------------------------------------- /scratch-space/__pycache__/me.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Software-Engineering-with-Python/f12cd71d475c5286947106174d94673a699bec1f/scratch-space/__pycache__/me.cpython-35.pyc -------------------------------------------------------------------------------- /scratch-space/__pycache__/test_me.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Software-Engineering-with-Python/f12cd71d475c5286947106174d94673a699bec1f/scratch-space/__pycache__/test_me.cpython-35.pyc -------------------------------------------------------------------------------- /scratch-space/inspect_me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A walk-through of the processes needed to perform automatic 4 | test-coverage detection and assurance. 5 | """ 6 | 7 | import inspect 8 | 9 | import me as target_module 10 | 11 | target_classes = set([ 12 | member[0] for member in 13 | inspect.getmembers(target_module, inspect.isclass) 14 | ]) 15 | # target_classes = { 16 | # 'Child', 'ChildOverride', 'Parent', 'Showable' 17 | # } at this point 18 | 19 | expected_cases = set([ 20 | 'test%s' % class_name 21 | for class_name in target_classes 22 | ] 23 | ) 24 | # expected_cases = { 25 | # 'testChild', 'testShowable', 'testChildOverride', 26 | # 'testParent' 27 | # } at this point 28 | 29 | import unittest 30 | 31 | import test_me as test_module 32 | 33 | test_cases = set([ 34 | member[0] for member in 35 | inspect.getmembers(test_module, inspect.isclass) 36 | if issubclass(member[1], unittest.TestCase) 37 | ]) 38 | # test_cases, before any TestCase classes have been defined, 39 | # is an empty set 40 | 41 | missing_tests = expected_cases.difference(test_cases) 42 | # missing_tests = { 43 | # 'testShowable', 'testChild', 'testParent', 44 | # 'testChildOverride' 45 | # } 46 | if missing_tests: 47 | print( 48 | 'Test-policies require test-case classes to be ' 49 | 'created for each class in the codebase. The ' 50 | 'following have not been created:\n * %s' % 51 | '\n * '.join(missing_tests) 52 | ) 53 | 54 | target_class = target_module.Parent 55 | 56 | from pprint import pprint 57 | import json 58 | 59 | property_tests = set() 60 | sourceMRO = list(target_class.__mro__) 61 | sourceMRO.reverse() 62 | # Get all the item's properties 63 | properties = [ 64 | member for member in inspect.getmembers( 65 | target_class, inspect.isdatadescriptor) 66 | if member[0][0:2] != '__' 67 | ] 68 | # sourceMRO = [ 69 | # , , 70 | # 71 | # ] 72 | # Create and populate data-structures that keep track of where 73 | # property-members originate from, and what their implementation 74 | # looks like. Initially populated with None values: 75 | propSources = {} 76 | propImplementations = {} 77 | for name, value in properties: 78 | propSources[name] = None 79 | propImplementations[name] = None 80 | # Populate the dictionaries based on the names found 81 | for memberName in propSources: 82 | implementation = target_class.__dict__.get(memberName) 83 | if implementation and propImplementations[memberName] != implementation: 84 | propImplementations[memberName] = implementation 85 | propSources[memberName] = target_class 86 | # propImplementations = { 87 | # "prop": 88 | # } 89 | # propSources = { 90 | # "prop": 91 | # } 92 | # If the target_class is changed to target_module.Child: 93 | # propImplementations = { 94 | # "prop": None 95 | # } 96 | # propSources = { 97 | # "prop": None 98 | # } 99 | property_tests = set( 100 | [ 101 | 'test%s' % key for key in propSources 102 | if propSources[key] == target_class 103 | ] 104 | ) 105 | # property_tests = {'testprop'} 106 | # If the target_class is changed to target_module.Child: 107 | # property_tests = set() 108 | 109 | target_class = target_module.Showable 110 | 111 | method_tests = set() 112 | sourceMRO = list(target_class.__mro__) 113 | sourceMRO.reverse() 114 | # Get all the item's methods 115 | methods = [ 116 | member for member in inspect.getmembers( 117 | target_class, inspect.isfunction) 118 | ] + [ 119 | member for member in inspect.getmembers( 120 | target_class, inspect.ismethod) 121 | ] 122 | # Create and populate data-structures that keep track of where 123 | # method-members originate from, and what their implementation 124 | # looks like. Initially populated with None values: 125 | methSources = {} 126 | methImplementations = {} 127 | for name, value in methods: 128 | if name.startswith('_%s__' % target_class.__name__): 129 | # Locally-defined private method - Don't test it 130 | continue 131 | if hasattr(value, '__isabstractmethod__') and value.__isabstractmethod__: 132 | # Locally-defined abstract method - Don't test it 133 | continue 134 | methSources[name] = None 135 | methImplementations[name] = None 136 | for memberName in methSources: 137 | implementation = target_class.__dict__.get(memberName) 138 | if implementation and methImplementations[memberName] != implementation: 139 | methImplementations[memberName] = implementation 140 | methSources[memberName] = target_class 141 | method_tests = set( 142 | [ 143 | 'test%s' % key for key in methSources 144 | if methSources[key] == target_class 145 | ] 146 | ) 147 | # method_tests = { 148 | # 'testpublic', 'test__init__', 'test_protected', 149 | # 'testshow' 150 | # } 151 | # If the target_class is changed to target_module.Child: 152 | # method_tests = set() 153 | # If the target_class is changed to target_module.Showable: 154 | # method_tests = set() 155 | -------------------------------------------------------------------------------- /scratch-space/me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A collection of classes to demonstrate unit-testing coverage-policy 4 | """ 5 | 6 | import abc 7 | 8 | __all__ = [ 9 | 'Showable', 'Parent', 'Child', 'ChildOverride' 10 | ] 11 | 12 | class Showable(metaclass=abc.ABCMeta): 13 | @abc.abstractmethod 14 | def show(self): 15 | pass 16 | 17 | class Parent(Showable): 18 | 19 | _lead_len = 33 20 | 21 | prop = property() 22 | 23 | def __init__(self, arg, *args, **kwargs): 24 | self.arg = arg 25 | self.args = args 26 | self.kwargs = kwargs 27 | 28 | def public(self): 29 | return ( 30 | ('%s.arg [public] ' % self.__class__.__name__).ljust( 31 | self.__class__._lead_len, '.') + ' %s' % self.arg 32 | ) 33 | 34 | def _protected(self): 35 | return ( 36 | ('%s.arg [protected] ' % self.__class__.__name__).ljust( 37 | self.__class__._lead_len, '.') + ' %s' % self.arg 38 | ) 39 | 40 | def __private(self): 41 | return ( 42 | ('%s.arg [private] ' % self.__class__.__name__).ljust( 43 | self.__class__._lead_len, '.') + ' %s' % self.arg 44 | ) 45 | 46 | def show(self): 47 | print(self.public()) 48 | print(self._protected()) 49 | print(self.__private()) 50 | 51 | class Child(Parent): 52 | pass 53 | 54 | class ChildOverride(Parent): 55 | 56 | def public(self): 57 | return ( 58 | ('%s.arg [PUBLIC] ' % self.__class__.__name__).ljust( 59 | self.__class__._lead_len, '.') + ' %s' % self.arg 60 | ) 61 | 62 | def _protected(self): 63 | return ( 64 | ('%s.arg [PROTECTED] ' % self.__class__.__name__).ljust( 65 | self.__class__._lead_len, '.') + ' %s' % self.arg 66 | ) 67 | 68 | def __private(self): 69 | return ( 70 | ('%s.arg [PRIVATE] ' % self.__class__.__name__).ljust( 71 | self.__class__._lead_len, '.') + ' %s' % self.arg 72 | ) 73 | 74 | if __name__ == '__main__': 75 | p = Parent('parent', 1, 2, 3, key='value') 76 | p.show() 77 | 78 | c = Child('child', 1, 2, 3, key='value') 79 | c.show() 80 | 81 | co = ChildOverride('child-override', 1, 2, 3, key='value') 82 | co.show() 83 | -------------------------------------------------------------------------------- /scratch-space/scope.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | An example of public, protected and private class-members 4 | """ 5 | 6 | import sys 7 | 8 | print('='*72) 9 | print('Python version: %s' % sys.version) 10 | print('='*72) 11 | 12 | class ExampleParent: 13 | 14 | def __init__(self): 15 | pass 16 | 17 | def public_method(self, arg, *args, **kwargs): 18 | print('%s.public_method called:' % self.__class__.__name__) 19 | print('+- arg ...... %s' % arg) 20 | print('+- args ..... %s' % str(args)) 21 | print('+- kwargs ... %s' % kwargs) 22 | 23 | def _protected_method(self, arg, *args, **kwargs): 24 | print('%s._protected_method called:' % self.__class__.__name__) 25 | print('+- arg ...... %s' % arg) 26 | print('+- args ..... %s' % str(args)) 27 | print('+- kwargs ... %s' % kwargs) 28 | 29 | def __private_method(self, arg, *args, **kwargs): 30 | print('%s.__private_method called:' % self.__class__.__name__) 31 | print('+- arg ...... %s' % arg) 32 | print('+- args ..... %s' % str(args)) 33 | print('+- kwargs ... %s' % kwargs) 34 | 35 | def show(self): 36 | self.public_method('example public', 1, 2, 3, key='value') 37 | self._protected_method('example "protected"', 1, 2, 3, key='value') 38 | self.__private_method('example "private"', 1, 2, 3, key='value') 39 | 40 | class ExampleChild(ExampleParent): 41 | pass 42 | 43 | parent = ExampleParent() 44 | parent.show() 45 | print('-'*72) 46 | print(dir(ExampleParent)) 47 | print('-'*72) 48 | child = ExampleChild() 49 | child.show() 50 | print('-'*72) 51 | print(dir(ExampleChild)) 52 | print('='*72) 53 | -------------------------------------------------------------------------------- /scratch-space/test_me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | 5 | from idic.unit_testing import * 6 | 7 | import me 8 | 9 | from me import * 10 | 11 | class testmeCodeCoverage(ModuleCoverageTest): 12 | _testModule = me 13 | 14 | @testmeCodeCoverage.AddPropertyTesting 15 | @testmeCodeCoverage.AddMethodTesting 16 | class testChild(unittest.TestCase): 17 | pass 18 | 19 | @testmeCodeCoverage.AddPropertyTesting 20 | @testmeCodeCoverage.AddMethodTesting 21 | class testChildOverride(unittest.TestCase): 22 | pass 23 | 24 | @testmeCodeCoverage.AddPropertyTesting 25 | @testmeCodeCoverage.AddMethodTesting 26 | class testParent(unittest.TestCase): 27 | pass 28 | 29 | @testmeCodeCoverage.AddPropertyTesting 30 | @testmeCodeCoverage.AddMethodTesting 31 | class testShowable(unittest.TestCase): 32 | pass 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /scratch-space/test_me1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | 5 | class testShowable(unittest.TestCase): 6 | pass 7 | 8 | class testParent(unittest.TestCase): 9 | pass 10 | 11 | class testChild(unittest.TestCase): 12 | pass 13 | 14 | class testChildOverride(unittest.TestCase): 15 | pass 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /standards/idic/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Package-header for the idic namespace. 4 | """ 5 | 6 | ####################################### 7 | # Any needed from __future__ imports # 8 | # Create an "__all__" list to support # 9 | # "from module import member" use # 10 | ####################################### 11 | 12 | __all__ = [ 13 | # Constants 14 | # Exceptions 15 | # Functions 16 | # ABC "interface" classes 17 | # ABC abstract classes 18 | # Concrete classes 19 | # Child packages and modules 20 | 'unit_testing', 21 | ] 22 | 23 | ####################################### 24 | # Module metadata/dunder-names # 25 | ####################################### 26 | 27 | __author__ = 'Brian D. Allbee' 28 | __version__ = '0.1' 29 | __copyright__ = 'Copyright 2018, Some rights reserved by Brian D. Allbee' 30 | __license__ = ( 31 | 'This work is licensed under a Creative Commons Attribution-' 32 | 'ShareAlike 4.0 International License ' 33 | '(http://creativecommons.org/licenses/by-sa/4.0/).' 34 | ) 35 | __credits__ = ['Brian D. Allbee'] 36 | __maintainer__ = 'Brian D. Allbee' 37 | __email__ = 'brian.allbee+idic.unit_testing@gmail.com' 38 | __status__ = 'Beta' 39 | 40 | ####################################### 41 | # Standard library imports needed # 42 | ####################################### 43 | 44 | ####################################### 45 | # Third-party imports needed # 46 | ####################################### 47 | 48 | # Uncomment this if there are abstract classes or "interfaces" 49 | # defined in the module... 50 | # import abc 51 | 52 | ####################################### 53 | # Local imports needed # 54 | ####################################### 55 | 56 | ####################################### 57 | # Initialization needed before member # 58 | # definition can take place # 59 | ####################################### 60 | 61 | ####################################### 62 | # Module-level Constants # 63 | ####################################### 64 | 65 | ####################################### 66 | # Custom Exceptions # 67 | ####################################### 68 | 69 | ####################################### 70 | # Module functions # 71 | ####################################### 72 | 73 | ####################################### 74 | # ABC "interface" classes # 75 | ####################################### 76 | 77 | ####################################### 78 | # Abstract classes # 79 | ####################################### 80 | 81 | ####################################### 82 | # Concrete classes # 83 | ####################################### 84 | 85 | ####################################### 86 | # Initialization needed after member # 87 | # definition is complete # 88 | ####################################### 89 | 90 | ####################################### 91 | # Imports needed after member # 92 | # definition (to resolve circular # 93 | # dependencies - avoid if at all # 94 | # possible # 95 | ####################################### 96 | 97 | ####################################### 98 | # Code to execute if the module is # 99 | # called directly # 100 | ####################################### 101 | 102 | if __name__ == '__main__': 103 | pass 104 | -------------------------------------------------------------------------------- /standards/idic/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Software-Engineering-with-Python/f12cd71d475c5286947106174d94673a699bec1f/standards/idic/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /standards/idic/__pycache__/unit_testing.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Software-Engineering-with-Python/f12cd71d475c5286947106174d94673a699bec1f/standards/idic/__pycache__/unit_testing.cpython-35.pyc -------------------------------------------------------------------------------- /standards/idic/unit_testing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Provides classes and functionality for augmenting the standard unit- 4 | testing capabilities and functionality of the unittest module. 5 | """ 6 | 7 | ####################################### 8 | # Any needed from __future__ imports # 9 | # Create an "__all__" list to support # 10 | # "from module import member" use # 11 | ####################################### 12 | 13 | __all__ = [ 14 | # Constants 15 | # Exceptions 16 | # Functions 17 | 'PrintTestResults', 18 | 'SaveTestReport', 19 | # ABC "interface" classes 20 | # ABC abstract classes 21 | # Concrete classes 22 | 'ModuleCoverageTest', 23 | ] 24 | 25 | ####################################### 26 | # Module metadata/dunder-names # 27 | ####################################### 28 | 29 | __author__ = 'Brian D. Allbee' 30 | __version__ = '0.1' 31 | __copyright__ = 'Copyright 2018, Some rights reserved by Brian D. Allbee' 32 | __license__ = ( 33 | 'This work is licensed under a Creative Commons Attribution-' 34 | 'ShareAlike 4.0 International License ' 35 | '(http://creativecommons.org/licenses/by-sa/4.0/).' 36 | ) 37 | __credits__ = ['Brian D. Allbee'] 38 | __maintainer__ = 'Brian D. Allbee' 39 | __email__ = 'brian.allbee+idic.unit_testing@gmail.com' 40 | __status__ = 'Beta' 41 | 42 | ####################################### 43 | # Standard library imports needed # 44 | ####################################### 45 | 46 | import inspect 47 | import sys 48 | import unittest 49 | 50 | ####################################### 51 | # Third-party imports needed # 52 | ####################################### 53 | 54 | ####################################### 55 | # Local imports needed # 56 | ####################################### 57 | 58 | ####################################### 59 | # Initialization needed before member # 60 | # definition can take place # 61 | ####################################### 62 | 63 | ####################################### 64 | # Module-level Constants # 65 | ####################################### 66 | 67 | ####################################### 68 | # Custom Exceptions # 69 | ####################################### 70 | 71 | ####################################### 72 | # Module functions # 73 | ####################################### 74 | 75 | def PrintTestResults(results): 76 | """Prints the results of a unit-test run when passed a TestResult object.""" 77 | print('#'*80) 78 | print('Unit-test results') 79 | print('#'*80) 80 | print('Tests were successful ..... %s' % (results.wasSuccessful())) 81 | print('Number of tests run ....... %s' % (results.testsRun)) 82 | try: 83 | print(' + Tests ran in ........... %0.2f seconds' % (results.runTime)) 84 | except AttributeError: 85 | print('No test run-time available.') 86 | print('Number of errors .......... %s' % (len(results.errors))) 87 | print('Number of failures ........ %s' % (len (results.failures))) 88 | print('Number of tests skipped ... %s' % (len (results.skipped))) 89 | print('#'*80) 90 | if results.skipped: 91 | print("SKIPPED") 92 | print('#' + '-'*78 + '#') 93 | itemCount = 0 94 | for theError in results.skipped: 95 | print(('%s\n - %s' % theError).strip()) 96 | print('#'*80) 97 | if results.failures: 98 | print("FAILURES") 99 | print('#' + '-'*78 + '#') 100 | itemCount = 0 101 | for theError in results.failures: 102 | itemCount += 1 103 | print(('%s\n - %s' % theError).strip()) 104 | if itemCount != len(results.failures): 105 | print('#' + '-'*78 + '#') 106 | print('#'*80) 107 | if results.errors: 108 | print("ERRORS") 109 | print('#' + '-'*78 + '#') 110 | itemCount = 0 111 | for theError in results.errors: 112 | itemCount += 1 113 | print(theError[1].strip()) 114 | if itemCount != len(results.errors): 115 | print('#' + '-'*78 + '#') 116 | print('#'*80) 117 | if results.failures or results.errors: 118 | print('Unit-test results') 119 | print('#'*80) 120 | print('Tests were successful ..... %s' % (results.wasSuccessful())) 121 | print('Number of tests run ....... %s' % (results.testsRun)) 122 | try: 123 | print(' + Tests ran in ........... %0.2f seconds' % (results.runTime)) 124 | except AttributeError: 125 | print('No test run-time available.') 126 | print('Number of errors .......... %s' % (len(results.errors))) 127 | print('Number of failures ........ %s' % (len (results.failures))) 128 | print('Number of tests skipped ... %s' % (len (results.skipped))) 129 | print('#'*80) 130 | 131 | def SaveTestReport(results, name, filePath): 132 | """ 133 | Writes the results of a unit-test run when passed a TestResult object. 134 | """ 135 | fp = open(filePath, 'w') 136 | fp.write(""" 137 | ################################################################################ 138 | Unit-test Results: %s 139 | #------------------------------------------------------------------------------# 140 | Tests were SUCCESSFUL 141 | Number of tests run ....... %d 142 | Number of tests skipped ... %d 143 | """ % (name, results.testsRun, len(results.skipped))) 144 | try: 145 | fp.write('Tests ran in .......... %0.3f seconds\n' % ( 146 | results.runTime) 147 | ) 148 | except AttributeError: 149 | pass 150 | if results.skipped: 151 | fp.write('#' + '-'*78 + '#\n') 152 | fp.write('List of skipped tests and the reasons they were skipped:\n') 153 | for skip in results.skipped: 154 | fp.write('%s\n - %s\n' % skip) 155 | fp.write('#' * 80) 156 | fp.close() 157 | 158 | ####################################### 159 | # ABC "interface" classes # 160 | ####################################### 161 | 162 | ####################################### 163 | # Abstract classes # 164 | ####################################### 165 | 166 | ####################################### 167 | # Concrete classes # 168 | ####################################### 169 | 170 | class ModuleCoverageTest(unittest.TestCase): 171 | """ 172 | A reusable unit-test that checks to make sure that all classes in the 173 | module being tested have corresponding test-case classes in the 174 | unit-test module where the derived class is defined. 175 | """ 176 | ################################### 177 | # Class attributes/constants # 178 | ################################### 179 | 180 | # - Class constants that point to the namespace and module 181 | # being tested 182 | _testNamespace = None 183 | _testModule = None 184 | 185 | ################################### 186 | # Property-getter methods # 187 | ################################### 188 | 189 | # def _get_property_name(self) -> str: 190 | # return self._property_name 191 | 192 | ################################### 193 | # Property-setter methods # 194 | ################################### 195 | 196 | # def _set_property_name(self, value:str) -> None: 197 | # # TODO: Type- and/or value-check the value argument of the 198 | # # setter-method, unless it's deemed unnecessary. 199 | # self._property_name = value 200 | 201 | ################################### 202 | # Property-deleter methods # 203 | ################################### 204 | 205 | # def _del_property_name(self) -> None: 206 | # self._property_name = None 207 | 208 | ################################### 209 | # Instance property definitions # 210 | ################################### 211 | 212 | # property_name = property( 213 | # # TODO: Remove setter and deleter if access is not needed 214 | # _get_property_name, _set_property_name, _del_property_name, 215 | # 'Gets, sets or deletes the property_name (str) of the instance' 216 | # ) 217 | 218 | ################################### 219 | # Object initialization # 220 | ################################### 221 | 222 | ################################### 223 | # Object deletion # 224 | ################################### 225 | 226 | ################################### 227 | # Instance methods # 228 | ################################### 229 | 230 | def testCodeCoverage(self): 231 | if not self.__class__._testModule: 232 | return 233 | self.assertEqual([], self._missingTestCases, 234 | 'Unit-testing policies require test-cases for all classes ' 235 | 'and functions in the %s module, but the following have not ' 236 | 'been defined: (%s)' % ( 237 | self.__class__._testModule.__name__, 238 | ', '.join(self._missingTestCases) 239 | ) 240 | ) 241 | 242 | ################################### 243 | # Overrides of built-in methods # 244 | ################################### 245 | 246 | ################################### 247 | # Class methods # 248 | ################################### 249 | 250 | @classmethod 251 | def AddMethodTesting(cls, target): 252 | if cls.__name__ == 'ModuleCoverageTest': 253 | raise RuntimeError('ModuleCoverageTest should be extended ' 254 | 'into a local test-case class, not used as one directly.') 255 | if not cls._testModule: 256 | raise AttributeError('%s does not have a _testModule defined ' 257 | 'as a class attribute. Check that the decorator-method is ' 258 | 'being called from the extended local test-case class, not ' 259 | 'from ModuleCoverageTest itself.' % (cls.__name__)) 260 | try: 261 | if cls._methodTestsByClass: 262 | populate = False 263 | else: 264 | populate = True 265 | except AttributeError: 266 | populate = True 267 | if populate: 268 | cls.setUpClass() 269 | def testMethodCoverage(self): 270 | requiredTestMethods = cls._methodTestsByClass[target.__name__] 271 | # print '###### requiredTestMethods: %s' % requiredTestMethods 272 | activeTestMethods = set( 273 | [ 274 | m[0] for m in 275 | inspect.getmembers(target, inspect.isfunction) 276 | if m[0][0:4] == 'test' 277 | ] 278 | ) 279 | missingMethods = sorted( 280 | requiredTestMethods.difference(activeTestMethods) 281 | ) 282 | self.assertEqual([], missingMethods, 283 | 'Unit-testing policy requires test-methods to be created for ' 284 | 'all public and protected methods, but %s is missing the ' 285 | 'following test-methods: %s' % ( 286 | target.__name__, missingMethods 287 | ) 288 | ) 289 | target.testMethodCoverage = testMethodCoverage 290 | return target 291 | 292 | @classmethod 293 | def AddPropertyTesting(cls, target): 294 | if cls.__name__ == 'ModuleCoverageTest': 295 | raise RuntimeError('ModuleCoverageTest should be extended ' 296 | 'into a local test-case class, not used as one directly.') 297 | if not cls._testModule: 298 | raise AttributeError('%s does not have a _testModule defined ' 299 | 'as a class attribute. Check that the decorator-method is ' 300 | 'being called from the extended local test-case class, not ' 301 | 'from ModuleCoverageTest itself.' % (cls.__name__)) 302 | try: 303 | if cls._propertyTestsByClass: 304 | populate = False 305 | else: 306 | populate = True 307 | except AttributeError: 308 | populate = True 309 | if populate: 310 | cls.setUpClass() 311 | def testPropertyCoverage(self): 312 | requiredTestMethods = cls._propertyTestsByClass[target.__name__] 313 | # print '###### requiredTestMethods: %s' % requiredTestMethods 314 | activeTestMethods = set( 315 | [ 316 | m[0] for m in 317 | inspect.getmembers(target, inspect.isfunction) 318 | if m[0][0:4] == 'test' 319 | ] 320 | ) 321 | missingMethods = sorted( 322 | requiredTestMethods.difference(activeTestMethods) 323 | ) 324 | self.assertEqual([], missingMethods, 325 | 'Unit-testing policy requires test-methods to be created for ' 326 | 'all public properties, but %s is missing the following test-' 327 | 'methods: %s' % (target.__name__, missingMethods) 328 | ) 329 | target.testPropertyCoverage = testPropertyCoverage 330 | return target 331 | 332 | @classmethod 333 | def setUpClass(cls): 334 | if not cls._testModule: 335 | cls._missingTestCases = [] 336 | return 337 | # Get all the classes available in the module 338 | cls._moduleClasses = inspect.getmembers( 339 | cls._testModule, inspect.isclass) 340 | # Get all the functions available in the module 341 | cls._moduleFunctions = inspect.getmembers( 342 | cls._testModule, inspect.isfunction) 343 | # Collect all the *LOCAL* items 344 | cls._testModuleName = cls._testModule.__name__ 345 | # Find and keep track of all of the test-cases that relate to 346 | # classes in the module being tested 347 | cls._classTests = dict( 348 | [ 349 | ('test%s' % m[0], m[1]) 350 | for m in cls._moduleClasses 351 | if m[1].__module__ == cls._testModuleName 352 | ] 353 | ) 354 | # Ditto for the functions in the module being tested 355 | cls._functionTests = dict( 356 | [ 357 | ('test%s' % m[0], m[1]) 358 | for m in cls._moduleFunctions 359 | if m[1].__module__ == cls._testModuleName 360 | ] 361 | ) 362 | # The list of required test-case class-names is the aggregated 363 | # list of all class- and function-test-case-class names 364 | cls._requiredTestCases = sorted( 365 | list(cls._classTests.keys()) + list(cls._functionTests.keys()) 366 | ) 367 | # Find and keep track of all of the actual test-case classes in 368 | # the module the class resides in 369 | cls._actualTestCases = dict( 370 | [ 371 | item for item in 372 | inspect.getmembers(inspect.getmodule(cls), 373 | inspect.isclass) 374 | if item[1].__name__[0:4] == 'test' 375 | and issubclass(item[1], unittest.TestCase) 376 | ] 377 | ) 378 | # Calculate the missing test-case-class names, for use by 379 | # the testCodeCoverage test-method 380 | cls._missingTestCases = sorted( 381 | set(cls._requiredTestCases).difference( 382 | set(cls._actualTestCases.keys()))) 383 | 384 | # Calculate the property test-case names for all the 385 | # module's classes 386 | cls._propertyTestsByClass = {} 387 | for testClass in cls._classTests: 388 | cls._propertyTestsByClass[testClass] = set() 389 | sourceClass = cls._classTests[testClass] 390 | sourceMRO = list(sourceClass.__mro__) 391 | sourceMRO.reverse() 392 | # Get all the item's properties 393 | properties = [ 394 | member for member in inspect.getmembers( 395 | sourceClass, inspect.isdatadescriptor) 396 | if member[0][0:2] != '__' 397 | ] 398 | # Create and populate data-structures that keep track of where 399 | # property-members originate from, and what their implementation 400 | # looks like. Initially populated with None values: 401 | propSources = {} 402 | propImplementations = {} 403 | for name, value in properties: 404 | propSources[name] = None 405 | propImplementations[name] = None 406 | for memberName in propSources: 407 | implementation = sourceClass.__dict__.get(memberName) 408 | if implementation \ 409 | and propImplementations[memberName] != implementation: 410 | propImplementations[memberName] = implementation 411 | propSources[memberName] = sourceClass 412 | cls._propertyTestsByClass[testClass] = set( 413 | [ 414 | 'test%s' % key for key in propSources 415 | if propSources[key] == sourceClass 416 | ] 417 | ) 418 | 419 | # Calculate the method test-case names for all the module's classes 420 | cls._methodTestsByClass = {} 421 | for testClass in cls._classTests: 422 | cls._methodTestsByClass[testClass] = set() 423 | sourceClass = cls._classTests[testClass] 424 | sourceMRO = list(sourceClass.__mro__) 425 | sourceMRO.reverse() 426 | # Get all the item's methods 427 | methods = [ 428 | member for member in inspect.getmembers( 429 | sourceClass, inspect.ismethod) 430 | ] + [ 431 | member for member in inspect.getmembers( 432 | sourceClass, inspect.isfunction) 433 | ] 434 | # Create and populate data-structures that keep track of where 435 | # method-members originate from, and what their implementation 436 | # looks like. Initially populated with None values: 437 | methSources = {} 438 | methImplementations = {} 439 | for name, value in methods: 440 | if name.startswith('_%s__' % sourceClass.__name__): 441 | # Locally-defined private method - Don't test it 442 | continue 443 | if hasattr(value, '__isabstractmethod__') \ 444 | and value.__isabstractmethod__: 445 | # Locally-defined abstract method - Don't test it 446 | continue 447 | methSources[name] = None 448 | methImplementations[name] = None 449 | for memberName in methSources: 450 | implementation = sourceClass.__dict__.get(memberName) 451 | if implementation \ 452 | and methImplementations[memberName] != implementation: 453 | methImplementations[memberName] = implementation 454 | methSources[memberName] = sourceClass 455 | cls._methodTestsByClass[testClass] = set( 456 | [ 457 | 'test%s' % key for key in methSources 458 | if methSources[key] == sourceClass 459 | ] 460 | ) 461 | 462 | ################################### 463 | # Static methods # 464 | ################################### 465 | 466 | ####################################### 467 | # Imports needed after member # 468 | # definition (to resolve circular # 469 | # dependencies - avoid if at all # 470 | # possible # 471 | ####################################### 472 | 473 | ####################################### 474 | # Code to execute if the module is # 475 | # called directly # 476 | ####################################### 477 | 478 | if __name__ == '__main__': 479 | from pprint import pprint 480 | pprint(StandardTestValuePolicy.All) 481 | 482 | -------------------------------------------------------------------------------- /tree.txt: -------------------------------------------------------------------------------- 1 | . 2 | ├── code-templates 3 | │   ├── class-abstract.py 4 | │   ├── class-concrete.py 5 | │   ├── class-interface.py 6 | │   ├── module.py 7 | │   └── package__init__.py 8 | ├── hms-artisan 9 | │   ├── bin 10 | │   ├── etc 11 | │   │   └── hms 12 | │   ├── Makefile 13 | │   ├── README.md 14 | │   ├── setup.py 15 | │   ├── src 16 | │   │   └── hms_artisan 17 | │   │      └── __init__.py 18 | │   └── var 19 | │   └── cache 20 | │   └── hms 21 | ├── hms-artisan.geany 22 | ├── hms-co-app 23 | │   ├── bin 24 | │   ├── etc 25 | │   │   └── hms 26 | │   ├── Makefile 27 | │   ├── README.md 28 | │   ├── setup.py 29 | │   ├── src 30 | │   │   └── hms_co 31 | │   │   └── __init__.py 32 | │   └── var 33 | │   └── cache 34 | │   └── hms 35 | ├── hms-co-app.geany 36 | ├── hms-core 37 | │   ├── bin 38 | │   ├── etc 39 | │   │   └── hms 40 | │   ├── Makefile 41 | │   ├── README.md 42 | │   ├── setup.py 43 | │   ├── src 44 | │   │   └── hms_core 45 | │   │   └── __init__.py 46 | │   └── var 47 | │   └── cache 48 | │   └── hms 49 | ├── hms-core.geany 50 | ├── hms-gateway 51 | │   ├── bin 52 | │   ├── etc 53 | │   │   └── hms 54 | │   ├── Makefile 55 | │   ├── README.md 56 | │   ├── setup.py 57 | │   ├── src 58 | │   │   └── hms_gateway 59 | │   │   └── __init__.py 60 | │   └── var 61 | │   └── cache 62 | │   └── hms 63 | ├── hms-gateway.geany 64 | ├── hms_sys.geany 65 | ├── README.md 66 | └── tree.txt 67 | 68 | 46 directories, 52 files 69 | --------------------------------------------------------------------------------