diff --git a/.release b/.release index 5f7499c33861c6effe998c0d59570c65ad41ba5d..3a4f11b25b10215b4d5e4ab63f9e8dfe14292f4a 100644 --- a/.release +++ b/.release @@ -1,2 +1,2 @@ -release=0.3.0 -tag=lmcbaseclasses-0.3.0 +release=0.3.1 +tag=lmcbaseclasses-0.3.1 diff --git a/Pipfile b/Pipfile index 82ea75a189ff7fbefd9f6e96097171a81d357c9a..3abc563f0ac5bd1ab5edd7cac229728129d75792 100644 --- a/Pipfile +++ b/Pipfile @@ -3,6 +3,11 @@ url = "https://pypi.org/simple" verify_ssl = true name = "pypi" +[[source]] +url = "https://nexus.engageska-portugal.pt/repository/pypi/simple" +verify_ssl = true +name = "nexus-hosted" + [packages] # numpy and pytango versions must match those in the ska-python-builder image, # otherwise pytango will be recompiled. @@ -13,6 +18,7 @@ pytango = "==9.3.1" # 'RUN ipython profile create' line from Dockerfile. itango = "*" future = "*" +ska_logging = ">=0.2.0" [dev-packages] docutils = "*" @@ -30,7 +36,6 @@ sphinx-autobuild = "*" sphinxcontrib-websupport = "*" coverage = "*" pytest-xdist = "*" -mock = "*" pylint2junit = "*" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index a0d2e2bb2a73c775d9eafa337f0910a706914419..12b3038a211884ffb38cf49f4c6eb3afb0832887 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9d082110a4902821907795ef941cf637503b027b438b0e27f6f38d757a359767" + "sha256": "1a0f0ad37d9bc766ae4bbcd4da90f0f21879deaa0530e7a7a2f848525ce8655e" }, "pipfile-spec": 6, "requires": { @@ -12,6 +12,11 @@ "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true + }, + { + "name": "nexus-hosted", + "url": "https://nexus.engageska-portugal.pt/repository/pypi/simple", + "verify_ssl": true } ] }, @@ -48,10 +53,10 @@ }, "ipython": { "hashes": [ - "sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280", - "sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995" + "sha256:0f4bcf18293fb666df8511feec0403bdb7e061a5842ea6e88a3177b0ceb34ead", + "sha256:387686dd7fc9caf29d2fddcf3116c4b07a11d9025701d220c589a430b0171d8a" ], - "version": "==7.9.0" + "version": "==7.11.1" }, "ipython-genutils": { "hashes": [ @@ -69,10 +74,10 @@ }, "jedi": { "hashes": [ - "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", - "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" + "sha256:1349c1e8c107095a55386628bb3b2a79422f3a2cab8381e34ce19909e0cf5064", + "sha256:e909527104a903606dd63bea6e8e888833f0ef087057829b89a18364a856f807" ], - "version": "==0.15.1" + "version": "==0.15.2" }, "numpy": { "hashes": [ @@ -98,10 +103,10 @@ }, "parso": { "hashes": [ - "sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", - "sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" + "sha256:55cf25df1a35fd88b878715874d2c4dc1ad3f0eebd1e0266a67e1f55efccfbe1", + "sha256:5c1f7791de6bd5dbbeac8db0ef5594b36799de198b3f7f7014643b0c5536b9d3" ], - "version": "==0.5.1" + "version": "==0.5.2" }, "pexpect": { "hashes": [ @@ -120,11 +125,10 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4", - "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31", - "sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db" + "sha256:0278d2f51b5ceba6ea8da39f76d15684e84c996b325475f6e5720edc584326a7", + "sha256:63daee79aa8366c8f1c637f1a4876b890da5fc92a19ebd2f7080ebacb901e990" ], - "version": "==2.0.10" + "version": "==3.0.2" }, "ptyprocess": { "hashes": [ @@ -135,10 +139,10 @@ }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.4.2" + "version": "==2.5.2" }, "pytango": { "hashes": [ @@ -160,6 +164,14 @@ ], "version": "==1.13.0" }, + "ska-logging": { + "hashes": [ + "sha256:1c9ea6bc9342c9851e241312386b4964c6c178501fbe9b8ff865e04908ed4e3f", + "sha256:7ddb0c25d848d486c733a394e9b24af1de93a91689981b72ba21091eea4052b9" + ], + "index": "pypi", + "version": "==0.2.1" + }, "traitlets": { "hashes": [ "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", @@ -169,10 +181,10 @@ }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", + "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" ], - "version": "==0.1.7" + "version": "==0.1.8" } }, "develop": { @@ -213,17 +225,17 @@ }, "babel": { "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" ], - "version": "==2.7.0" + "version": "==2.8.0" }, "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.9.11" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -234,50 +246,48 @@ }, "coverage": { "hashes": [ - "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", - "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", - "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", - "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", - "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", - "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", - "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", - "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", - "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", - "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", - "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", - "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", - "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", - "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", - "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", - "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", - "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", - "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", - "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", - "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", - "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", - "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", - "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", - "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", - "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", - "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", - "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", - "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", - "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", - "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", - "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", - "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" + "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", + "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", + "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", + "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", + "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", + "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", + "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", + "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", + "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", + "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", + "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", + "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", + "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", + "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", + "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", + "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", + "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", + "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", + "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", + "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", + "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", + "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", + "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", + "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", + "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", + "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", + "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", + "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", + "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", + "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", + "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" ], "index": "pypi", - "version": "==4.5.4" + "version": "==5.0.3" }, "docutils": { "hashes": [ - "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", - "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", - "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" ], "index": "pypi", - "version": "==0.15.2" + "version": "==0.16" }, "execnet": { "hashes": [ @@ -295,18 +305,18 @@ }, "imagesize": { "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", + "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359", + "sha256:f17c015735e1a88296994c0697ecea7e11db24290941983b08c9feb30921e6d8" ], "markers": "python_version < '3.8'", - "version": "==0.23" + "version": "==1.4.0" }, "isort": { "hashes": [ @@ -396,27 +406,19 @@ ], "version": "==0.6.1" }, - "mock": { - "hashes": [ - "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", - "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" - ], - "index": "pypi", - "version": "==3.0.5" - }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:1a2a32c72400d365000412fe08eb4a24ebee89997c18d3d147544f70f5403b39", + "sha256:c468adec578380b6281a114cb8a5db34eb1116277da92d7c46f904f0b52d3288" ], - "version": "==7.2.0" + "version": "==8.1.0" }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb", + "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8" ], - "version": "==19.2" + "version": "==20.0" }, "pathtools": { "hashes": [ @@ -426,10 +428,10 @@ }, "pluggy": { "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.13.0" + "version": "==0.13.1" }, "port-for": { "hashes": [ @@ -439,17 +441,17 @@ }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.4.2" + "version": "==2.5.2" }, "pylint": { "hashes": [ @@ -468,18 +470,18 @@ }, "pyparsing": { "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" ], - "version": "==2.4.5" + "version": "==2.4.6" }, "pytest": { "hashes": [ - "sha256:1897d74f60a5d8be02e06d708b41bf2445da2ee777066bd68edf14474fc201eb", - "sha256:f6a567e20c04259d41adce9a360bd8991e6aa29dd9695c5e6bd25a9779272673" + "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", + "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" ], "index": "pypi", - "version": "==5.3.0" + "version": "==5.3.2" }, "pytest-cov": { "hashes": [ @@ -498,11 +500,11 @@ }, "pytest-json-report": { "hashes": [ - "sha256:33699dcd3389770f0db47c1a104d1746f8d42f8da16747dc807dd8a1143b5c74", - "sha256:3f81f20586223f1cc97c0f60f1dea5f8833ea2493505282abb9ce06a9f45ebdc" + "sha256:08182e00de9eb1735274fe4b91b0cf7314e138c0dac7e4ae424c3ec469787a2b", + "sha256:628b2a3a8f6edf069bf502ad8f65694ce6f2e4f86e73db442f18122389dafc38" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.2.1" }, "pytest-metadata": { "hashes": [ @@ -522,11 +524,11 @@ }, "pytest-xdist": { "hashes": [ - "sha256:5d1b1d4461518a6023d56dab62fb63670d6f7537f23e2708459a557329accf48", - "sha256:a8569b027db70112b290911ce2ed732121876632fb3f40b1d39cd2f72f58b147" + "sha256:0f46020d3d9619e6d17a65b5b989c1ebbb58fc7b1da8fb126d70f4bac4dfeed1", + "sha256:7dc0d027d258cd0defc618fb97055fbd1002735ca7a6d17037018cf870e24011" ], "index": "pypi", - "version": "==1.30.0" + "version": "==1.31.0" }, "python-dotenv": { "hashes": [ @@ -545,21 +547,19 @@ }, "pyyaml": { "hashes": [ - "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", - "sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", - "sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", - "sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", - "sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", - "sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", - "sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", - "sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", - "sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", - "sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", - "sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", - "sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", - "sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8" - ], - "version": "==5.1.2" + "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", + "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", + "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", + "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", + "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", + "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", + "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", + "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", + "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", + "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", + "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" + ], + "version": "==5.3" }, "requests": { "hashes": [ @@ -584,11 +584,11 @@ }, "sphinx": { "hashes": [ - "sha256:31088dfb95359384b1005619827eaee3056243798c62724fd3fa4b84ee4d71bd", - "sha256:52286a0b9d7caa31efee301ec4300dbdab23c3b05da1c9024b4e84896fb73d79" + "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", + "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5" ], "index": "pypi", - "version": "==2.2.1" + "version": "==2.3.1" }, "sphinx-autobuild": { "hashes": [ @@ -670,29 +670,30 @@ }, "typed-ast": { "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" ], "markers": "implementation_name == 'cpython' and python_version < '3.8'", - "version": "==1.4.0" + "version": "==1.4.1" }, "urllib3": { "hashes": [ @@ -709,14 +710,13 @@ }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", + "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" ], - "version": "==0.1.7" + "version": "==0.1.8" }, "wrapt": { "hashes": [ - "sha256:1363a8cb032e4d21202a32e1172ec29dab453e30c88ec2351d4a215e6af749f0", "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" ], "version": "==1.11.2" diff --git a/docs/source/conf.py b/docs/source/conf.py index 99993da52123a2ab8fff536c686709edb83a6b07..3427377893fcd63740c764cb6cd603edecd9ea12 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ # import sys # import os -# from mock import Mock as MagicMock +# from unittest.mock import Mock as MagicMock # # class Mock(MagicMock): # @classmethod @@ -32,7 +32,7 @@ autodoc_mock_imports = ['PyTango', 'tango', 'tango.server', 'run', 'DeviceMeta', import sys import os -from mock import Mock as MagicMock +from unittest.mock import Mock as MagicMock class Mock(MagicMock): @classmethod diff --git a/setup.py b/setup.py index 6f0b039e9e918e8bd70496250c9fcea4602f5004..807e1395d04527db06c681cec4812871738024bb 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setuptools.setup( setup_requires=[] + pytest_runner, install_requires=[ "future", - "ska_logging" + "ska_logging >= 0.2.0" ], keywords="lmc base classes ska", zip_safe=False) diff --git a/skabase/SKAAlarmHandler/SKAAlarmHandler.xmi b/skabase/SKAAlarmHandler/SKAAlarmHandler.xmi index 414fcbbf891a2ad041cdca54845a96fe94c7b06d..11482df1b37050ec1d006abf210e4da611d6f29f 100644 --- a/skabase/SKAAlarmHandler/SKAAlarmHandler.xmi +++ b/skabase/SKAAlarmHandler/SKAAlarmHandler.xmi @@ -31,7 +31,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0"> <argin description="none"> @@ -232,7 +231,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKAAlarmHandler/test/SKAAlarmHandler_test.py b/skabase/SKAAlarmHandler/test/SKAAlarmHandler_test.py index 142a75b3d3743206d55336c038ccfdbf2c1452ca..535f61068113fb17eb3c9d494f5403a438607e89 100644 --- a/skabase/SKAAlarmHandler/test/SKAAlarmHandler_test.py +++ b/skabase/SKAAlarmHandler/test/SKAAlarmHandler_test.py @@ -34,7 +34,7 @@ class TestSKAAlarmHandler(object): 'AlarmConfigFile': '', 'SkaLevel': '4', 'GroupDefinitions': '', - 'LoggingTargetsDefault': ['console::cout'] + 'LoggingTargetsDefault': '' } @classmethod diff --git a/skabase/SKABaseDevice/SKABaseDevice.py b/skabase/SKABaseDevice/SKABaseDevice.py index fce01a6bbd4bc9c8960433f4863269fba030b9da..ffe4318199f2bbd6216f579d60ec354276ff8a25 100644 --- a/skabase/SKABaseDevice/SKABaseDevice.py +++ b/skabase/SKABaseDevice/SKABaseDevice.py @@ -16,12 +16,9 @@ import logging import logging.handlers import os import sys -import time +import threading from future.utils import with_metaclass -from logging import StreamHandler -from logging.handlers import SysLogHandler -from logging.handlers import RotatingFileHandler # Tango imports import tango @@ -31,6 +28,7 @@ from tango import AttrQuality, AttrWriteType from tango import DeviceProxy, DevFailed # SKA specific imports +import ska_logging from skabase import release file_path = os.path.dirname(os.path.abspath(__file__)) auxiliary_path = os.path.abspath(os.path.join(file_path, os.pardir)) + "/auxiliary" @@ -61,102 +59,126 @@ class TangoLoggingLevel(enum.IntEnum): DEBUG = int(tango.LogLevel.LOG_DEBUG) -def _sanitise_logging_targets(targets, device_name): - """Validate and return logging targets '<type>::<name>' strings. +class _Log4TangoLoggingLevel(enum.IntEnum): + """Python enumerated type for TANGO log4tango logging levels. - :param target: - List of candidate logging target strings, like '<type>[::<name>]' + This is different to tango.LogLevel, and is required if using + a device's set_log_level() method. It is not currently exported + via PyTango, so we hard code it here in the interim. + + Source: + https://github.com/tango-controls/cppTango/blob/ + 4feffd7c8e24b51c9597a40b9ef9982dd6e99cdf/log4tango/include/log4tango/Level.hh#L86-L93 + """ + OFF = 100 + FATAL = 200 + ERROR = 300 + WARN = 400 + INFO = 500 + DEBUG = 600 - :param device_name: - TANGO device name, like 'domain/family/member', used - for the default file name - :return: list of '<type>::<name>' strings, with default name, if applicable +class LoggingUtils: + """Utility functions to aid logger configuration. - :raises: LoggingTargetError for invalid target string that cannot be corrected + These functions are encapsulated in class to aid testing - it + allows dependent functions to be mocked. """ - default_target_names = { - "console": "cout", - "file": "{}.log".format(device_name.replace("/", "_")), - "syslog": None} - valid_targets = [] - for target in targets: + @staticmethod + def sanitise_logging_targets(targets, device_name): + """Validate and return logging targets '<type>::<name>' strings. + + :param target: + List of candidate logging target strings, like '<type>[::<name>]' + Empty and whitespace-only strings are ignored. + + :param device_name: + TANGO device name, like 'domain/family/member', used + for the default file name + + :return: list of '<type>::<name>' strings, with default name, if applicable + + :raises: LoggingTargetError for invalid target string that cannot be corrected + """ + default_target_names = { + "console": "cout", + "file": "{}.log".format(device_name.replace("/", "_")), + "syslog": None} + + valid_targets = [] + for target in targets: + target = target.strip() + if not target: + continue + if "::" in target: + target_type, target_name = target.split("::", 1) + else: + target_type = target + target_name = None + if target_type not in default_target_names: + raise LoggingTargetError( + "Invalid target type: {} - options are {}".format( + target_type, list(default_target_names.keys()))) + if not target_name: + target_name = default_target_names[target_type] + if not target_name: + raise LoggingTargetError( + "Target name required for type {}".format(target_type)) + valid_target = "{}::{}".format(target_type, target_name) + valid_targets.append(valid_target) + + return valid_targets + + @staticmethod + def create_logging_handler(target): + """Create a Python log handler based on the target type (console, file, syslog) + + :param target: Logging target for logger, <type>::<name> + + :return: StreamHandler, RotatingFileHandler, or SysLogHandler + + :raises: LoggingTargetError for invalid target string + """ if "::" in target: target_type, target_name = target.split("::", 1) else: - target_type = target - target_name = None - if target_type not in default_target_names: raise LoggingTargetError( - "Invalid target type: {} - options are {}".format( - target_type, list(default_target_names.keys()))) - if not target_name: - target_name = default_target_names[target_type] - if not target_name: + "Invalid target requested - missing '::' separator: {}".format(target)) + if target_type == "console": + handler = logging.StreamHandler(sys.stdout) + elif target_type == "file": + log_file_name = target_name + handler = logging.handlers.RotatingFileHandler( + log_file_name, 'a', LOG_FILE_SIZE, 2, None, False) + elif target_type == "syslog": + handler = logging.handlers.SysLogHandler( + address=target_name, facility=logging.handlers.SysLogHandler.LOG_SYSLOG) + else: raise LoggingTargetError( - "Target name required for type {}".format(target_type)) - valid_target = "{}::{}".format(target_type, target_name) - valid_targets.append(valid_target) - - return valid_targets + "Invalid target type requested: '{}' in '{}'".format(target_type, target)) + formatter = ska_logging.get_default_formatter(tags=True) + handler.setFormatter(formatter) + handler.name = target + return handler + @staticmethod + def update_logging_handlers(targets, logger): + old_targets = [handler.name for handler in logger.handlers] + added_targets = set(targets) - set(old_targets) + removed_targets = set(old_targets) - set(targets) -def _create_logging_handler(target, device_name): - """Create a Python log handler based on the target type (console, file, syslog) + for handler in list(logger.handlers): + if handler.name in removed_targets: + logger.removeHandler(handler) + for target in targets: + if target in added_targets: + handler = LoggingUtils.create_logging_handler(target) + logger.addHandler(handler) - :param target: Logging target for logger, <type>::<name> + logger.info('Logging targets set to %s', targets) - :param device_name: TANGO device name - :return: StreamHandler, RotatingFileHandler, or SysLogHandler - """ - target_type, target_name = target.split("::", 1) - - class UTCFormatter(logging.Formatter): - converter = time.gmtime - - # Format defined here: - # https://developer.skatelescope.org/en/latest/development/logging-format.html - # VERSION "|" TIMESTAMP "|" SEVERITY "|" [THREAD-ID] "|" [FUNCTION] "|" [LINE-LOC] "|" - # [TAGS] "|" MESSAGE LF - formatter = UTCFormatter( - fmt="1|" - "%(asctime)s.%(msecs)03dZ|" - "%(levelname)s|" - "%(threadName)s|" - "%(funcName)s|" - "%(filename)s#%(lineno)d|" - "tango-device:{}|" - "%(message)s".format(device_name), - datefmt='%Y-%m-%dT%H:%M:%S') - - if target_type == "console": - handler = StreamHandler(sys.stdout) - elif target_type == "file": - log_file_name = target_name - handler = RotatingFileHandler(log_file_name, 'a', LOG_FILE_SIZE, 2, None, False) - elif target_type == "syslog": - handler = SysLogHandler(address=target_name, facility='syslog') - handler.setFormatter(formatter) - handler.name = target - return handler - - -def _update_logging_handlers(targets, logger, device_name): - old_targets = [handler.name for handler in logger.handlers] - added_targets = set(targets) - set(old_targets) - removed_targets = set(old_targets) - set(targets) - - for handler in list(logger.handlers): - if handler.name in removed_targets: - logger.removeHandler(handler) - for target in targets: - if target in added_targets: - handler = _create_logging_handler(target, device_name) - logger.addHandler(handler) - - logger.info('Logging targets set to %s', targets) # PROTECTED REGION END # // SKABaseDevice.additionnal_import @@ -170,6 +192,9 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): """ # PROTECTED REGION ID(SKABaseDevice.class_variable) ENABLED START # + _logging_config_lock = threading.Lock() + _logging_configured = False + def _init_logging(self): """ This method initializes the logging mechanism, based on default properties. @@ -178,10 +203,40 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): :return: None. """ - self.logger = logging.getLogger(__name__) - # device may be reinitialised, so remove existing handlers + + class EnsureTagsFilter(logging.Filter): + """Ensure all records have a "tags" field - empty string, if not provided.""" + def filter(self, record): + if not hasattr(record, "tags"): + record.tags = "" + return True + + # There may be multiple devices in a single device server - these will all be + # starting at the same time, so use a lock to prevent race conditions, and + # a flag to ensure the SKA standard logging configuration is only applied once. + with SKABaseDevice._logging_config_lock: + if not SKABaseDevice._logging_configured: + ska_logging.configure_logging(tags_filter=EnsureTagsFilter) + SKABaseDevice._logging_configured = True + + device_name = self.get_name() + self.logger = logging.getLogger(device_name) + # device may be reinitialised, so remove existing handlers and filters for handler in list(self.logger.handlers): self.logger.removeHandler(handler) + for filt in list(self.logger.filters): + self.logger.removeFilter(filt) + + # add a filter with this device's name + device_name_tag = "tango-device:{}".format(device_name) + + class TangoDeviceTagsFilter(logging.Filter): + def filter(self, record): + record.tags = device_name_tag + return True + + self.logger.addFilter(TangoDeviceTagsFilter()) + # initialise using defaults in device properties self._logging_level = None self.write_loggingLevel(self.LoggingLevelDefault) @@ -225,38 +280,6 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): raise Exception(msg) return args_dict - def dev_logging(self, dev_log_msg, dev_log_level): - """ - DEPRECATED: Log the message to the Python logger. - - DEPRECATED - Use ``self.logger`` directly instead. For example, - ``self.logger.info("My message")`` for something at LOG_INFO level. - - :param dev_log_msg: DevString. - Message to log - - :param dev_log_level: DevEnum - Logging level of the message. The message can have one of the following - logging levels: - LOG_FATAL - LOG_ERROR - LOG_WARN - LOG_INFO - LOG_DEBUG - - :return: None - """ - if dev_log_level == TangoLoggingLevel.FATAL: - self.logger.fatal(dev_log_msg) - elif dev_log_level == TangoLoggingLevel.ERROR: - self.logger.error(dev_log_msg) - elif dev_log_level == TangoLoggingLevel.WARNING: - self.logger.warning(dev_log_msg) - elif dev_log_level == TangoLoggingLevel.INFO: - self.logger.info(dev_log_msg) - elif dev_log_level == TangoLoggingLevel.DEBUG: - self.logger.debug(dev_log_msg) - # PROTECTED REGION END # // SKABaseDevice.class_variable # ----------------- @@ -276,7 +299,7 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): ) LoggingTargetsDefault = device_property( - dtype='DevVarStringArray', default_value=["console::cout"] + dtype='DevVarStringArray', default_value=[] ) # ---------- @@ -304,7 +327,7 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): dtype=('str',), access=AttrWriteType.READ_WRITE, max_dim_x=3, - doc="Current logging targets for this device" + doc="Logging targets for this device, excluding ska_logging defaults" " - initialises to LoggingTargetsDefault on startup", ) @@ -447,30 +470,38 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): def write_loggingLevel(self, value): # PROTECTED REGION ID(SKABaseDevice.loggingLevel_write) ENABLED START # """ - Sets logging level for the device. + Sets logging level for the device. Both the Python logger and the + Tango logger are updated. :param value: Logging level for logger :return: None. """ self._logging_level = TangoLoggingLevel(value) + tango_logger = self.get_logger() if self._logging_level == TangoLoggingLevel.OFF: self.logger.setLevel(logging.CRITICAL) # not allowed to be "off" + tango_logger.set_level(_Log4TangoLoggingLevel.OFF) elif self._logging_level == TangoLoggingLevel.FATAL: self.logger.setLevel(logging.CRITICAL) + tango_logger.set_level(_Log4TangoLoggingLevel.FATAL) elif self._logging_level == TangoLoggingLevel.ERROR: self.logger.setLevel(logging.ERROR) + tango_logger.set_level(_Log4TangoLoggingLevel.ERROR) elif self._logging_level == TangoLoggingLevel.WARNING: self.logger.setLevel(logging.WARNING) + tango_logger.set_level(_Log4TangoLoggingLevel.WARN) elif self._logging_level == TangoLoggingLevel.INFO: self.logger.setLevel(logging.INFO) + tango_logger.set_level(_Log4TangoLoggingLevel.INFO) elif self._logging_level == TangoLoggingLevel.DEBUG: self.logger.setLevel(logging.DEBUG) + tango_logger.set_level(_Log4TangoLoggingLevel.DEBUG) else: raise LoggingLevelError( "Invalid level - {} - must be between {} and {}".format( self._logging_level, TangoLoggingLevel.OFF, TangoLoggingLevel.DEBUG)) - self.logger.info('Logging level set to %s', self._logging_level) + self.logger.info('Logging level set to %s on Python and Tango loggers', self._logging_level) # PROTECTED REGION END # // SKABaseDevice.loggingLevel_write def read_loggingTargets(self): @@ -493,8 +524,8 @@ class SKABaseDevice(with_metaclass(DeviceMeta, Device)): :return: None. """ device_name = self.get_name() - valid_targets = _sanitise_logging_targets(value, device_name) - _update_logging_handlers(valid_targets, self.logger, device_name) + valid_targets = LoggingUtils.sanitise_logging_targets(value, device_name) + LoggingUtils.update_logging_handlers(valid_targets, self.logger) # PROTECTED REGION END # // SKABaseDevice.loggingTargets_write def read_healthState(self): @@ -640,8 +671,6 @@ def main(args=None, **kwargs): :return: """ - # Do basic logging config before starting any threads - logging.basicConfig() return run((SKABaseDevice,), args=args, **kwargs) # PROTECTED REGION END # // SKABaseDevice.main diff --git a/skabase/SKABaseDevice/SKABaseDevice.xmi b/skabase/SKABaseDevice/SKABaseDevice.xmi index 6de185c82eec1f05c2d4f27913344d0e9dcd8c1e..8b83ecfc9de8b856087ab3c89a45391a5f8dbb4c 100644 --- a/skabase/SKABaseDevice/SKABaseDevice.xmi +++ b/skabase/SKABaseDevice/SKABaseDevice.xmi @@ -22,7 +22,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="false" concrete="true" concreteHere="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false"> <argin description="none"> @@ -133,7 +132,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="false" concrete="true" concreteHere="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="false" concrete="true" concreteHere="true"/> diff --git a/skabase/SKABaseDevice/test/SKABaseDevice_test.py b/skabase/SKABaseDevice/test/SKABaseDevice_test.py index ac378e38fa9c7153bb12aa1f3cad7d7e883e1ab6..0694fbb2042e93aeb3c42e7ec278f79239afc6e0 100644 --- a/skabase/SKABaseDevice/test/SKABaseDevice_test.py +++ b/skabase/SKABaseDevice/test/SKABaseDevice_test.py @@ -13,7 +13,6 @@ import sys import os import re import pytest -from tango import DevState # Imports from skabase.SKABaseDevice import SKABaseDevice @@ -24,13 +23,11 @@ sys.path.insert(0, os.path.abspath(path)) # PROTECTED REGION ID(SKABaseDevice.test_additional_imports) ENABLED START # import logging -import mock -from tango import DevFailed +from unittest import mock +from tango import DevFailed, DevState from skabase.SKABaseDevice import TangoLoggingLevel from skabase.SKABaseDevice.SKABaseDevice import ( - _create_logging_handler, - _sanitise_logging_targets, - _update_logging_handlers, + LoggingUtils, LoggingTargetError, ) # PROTECTED REGION END # // SKABaseDevice.test_additional_imports @@ -38,83 +35,144 @@ from skabase.SKABaseDevice.SKABaseDevice import ( # PROTECTED REGION ID(SKABaseDevice.test_SKABaseDevice_decorators) ENABLED START # -@pytest.fixture(params=[ - (["console"], ["console::cout"]), - (["console::"], ["console::cout"]), - (["console::cout"], ["console::cout"]), - (["console::anything"], ["console::anything"]), - (["file"], ["file::my_dev_name.log"]), - (["file::"], ["file::my_dev_name.log"]), - (["file::/tmp/dummy"], ["file::/tmp/dummy"]), - (["syslog::some/address"], ["syslog::some/address"]), - (["console", "file"], ["console::cout", "file::my_dev_name.log"]), - ]) -def good_logging_targets(request): - targets_in, expected = request.param - dev_name = "my/dev/name" - return targets_in, dev_name, expected - - -@pytest.fixture(params=[ - [""], - ["invalid"], - ["invalid", "console"], - ["invalid::type"], - ["syslog"], - ]) -def bad_logging_targets(request): - targets_in = request.param - dev_name = "my/dev/name" - return targets_in, dev_name - - -def test_sanitise_logging_targets_success(good_logging_targets): - targets_in, dev_name, expected = good_logging_targets - actual = _sanitise_logging_targets(targets_in, dev_name) - assert actual == expected - - -def test_sanitise_logging_targets_fail(bad_logging_targets): - targets_in, dev_name = bad_logging_targets - with pytest.raises(LoggingTargetError): - _sanitise_logging_targets(targets_in, dev_name) - - -def test_update_logging_handlers(): - logger = logging.Logger('testing') - dev_name = "my/dev/name" - - new_targets = ["console::cout"] - _update_logging_handlers(new_targets, logger, dev_name) - assert len(logger.handlers) == 1 - assert isinstance(logger.handlers[0], logging.StreamHandler) - - # test same handler is retained for same request - old_handler = logger.handlers[0] - new_targets = ["console::cout"] - _update_logging_handlers(new_targets, logger, dev_name) - assert len(logger.handlers) == 1 - assert logger.handlers[0] is old_handler - - # test other valid target types - new_targets = ["console::cout", "file::/tmp/dummy", "syslog::some/address"] - _update_logging_handlers(new_targets, logger, dev_name) - assert len(logger.handlers) == 3 - assert isinstance(logger.handlers[0], logging.StreamHandler) - assert isinstance(logger.handlers[1], logging.handlers.RotatingFileHandler) - assert isinstance(logger.handlers[2], logging.handlers.SysLogHandler) - - # test clearing of 1 handler - new_targets = ["console::cout", "syslog::some/address"] - _update_logging_handlers(new_targets, logger, dev_name) - assert len(logger.handlers) == 2 - assert isinstance(logger.handlers[0], logging.StreamHandler) - assert isinstance(logger.handlers[1], logging.handlers.SysLogHandler) - - # test clearing all handlers - new_targets = [] - _update_logging_handlers(new_targets, logger, dev_name) - assert len(logger.handlers) == 0 +class TestLoggingUtils: + @pytest.fixture(params=[ + ([""], []), + ([" \n\t "], []), + (["console"], ["console::cout"]), + (["console::"], ["console::cout"]), + (["console::cout"], ["console::cout"]), + (["console::anything"], ["console::anything"]), + (["file"], ["file::my_dev_name.log"]), + (["file::"], ["file::my_dev_name.log"]), + (["file::/tmp/dummy"], ["file::/tmp/dummy"]), + (["syslog::some/address"], ["syslog::some/address"]), + (["console", "file"], ["console::cout", "file::my_dev_name.log"]), + ]) + def good_logging_targets(self, request): + targets_in, expected = request.param + dev_name = "my/dev/name" + return targets_in, dev_name, expected + + + @pytest.fixture(params=[ + ["invalid"], + ["invalid", "console"], + ["invalid::type"], + ["syslog"], + ]) + def bad_logging_targets(self, request): + targets_in = request.param + dev_name = "my/dev/name" + return targets_in, dev_name + + + def test_sanitise_logging_targets_success(self, good_logging_targets): + targets_in, dev_name, expected = good_logging_targets + actual = LoggingUtils.sanitise_logging_targets(targets_in, dev_name) + assert actual == expected + + + def test_sanitise_logging_targets_fail(self, bad_logging_targets): + targets_in, dev_name = bad_logging_targets + with pytest.raises(LoggingTargetError): + LoggingUtils.sanitise_logging_targets(targets_in, dev_name) + + + @mock.patch('logging.handlers.SysLogHandler') + @mock.patch('logging.handlers.RotatingFileHandler') + @mock.patch('logging.StreamHandler') + @mock.patch('ska_logging.get_default_formatter') + def test_create_logging_handler(self, + mock_get_formatter, + mock_stream_handler, + mock_file_handler, + mock_syslog_handler): + # Expect formatter be created using `get_default_formatter(tags=True)` + # Use some mocks to check this. + mock_formatter = mock.MagicMock() + + def get_formatter_if_tags_enabled(*args, **kwargs): + if kwargs.get("tags", False): + return mock_formatter + + mock_get_formatter.side_effect = get_formatter_if_tags_enabled + + handler = LoggingUtils.create_logging_handler("console::cout") + assert handler == mock_stream_handler() + handler.setFormatter.assert_called_once_with(mock_formatter) + + handler = LoggingUtils.create_logging_handler("file::/tmp/dummy") + assert handler == mock_file_handler() + handler.setFormatter.assert_called_once_with(mock_formatter) + + handler = LoggingUtils.create_logging_handler("syslog::some/address") + assert handler == mock_syslog_handler() + handler.setFormatter.assert_called_once_with(mock_formatter) + + with pytest.raises(LoggingTargetError): + LoggingUtils.create_logging_handler("invalid::target") + + with pytest.raises(LoggingTargetError): + LoggingUtils.create_logging_handler("invalid") + + def test_update_logging_handlers(self): + logger = logging.getLogger('testing') + + def null_creator(target): + handler = logging.NullHandler() + handler.name = target + return handler + + orig_create_logging_handler = LoggingUtils.create_logging_handler + try: + mocked_creator = mock.MagicMock(side_effect=null_creator) + LoggingUtils.create_logging_handler = mocked_creator + + # test adding first handler + new_targets = ["console::cout"] + LoggingUtils.update_logging_handlers(new_targets, logger) + assert len(logger.handlers) == 1 + mocked_creator.assert_called_once_with("console::cout") + + # test same handler is retained for same request + old_handler = logger.handlers[0] + new_targets = ["console::cout"] + mocked_creator.reset_mock() + LoggingUtils.update_logging_handlers(new_targets, logger) + assert len(logger.handlers) == 1 + assert logger.handlers[0] is old_handler + mocked_creator.assert_not_called() + + # test other valid target types + new_targets = ["console::cout", "file::/tmp/dummy", "syslog::some/address"] + mocked_creator.reset_mock() + LoggingUtils.update_logging_handlers(new_targets, logger) + assert len(logger.handlers) == 3 + assert mocked_creator.call_count == 2 + mocked_creator.assert_has_calls( + [mock.call("file::/tmp/dummy"), + mock.call("syslog::some/address")], + any_order=True, + ) + + # test clearing of 1 handler + new_targets = ["console::cout", "syslog::some/address"] + mocked_creator.reset_mock() + LoggingUtils.update_logging_handlers(new_targets, logger) + assert len(logger.handlers) == 2 + mocked_creator.assert_not_called() + + + # test clearing all handlers + new_targets = [] + mocked_creator.reset_mock() + LoggingUtils.update_logging_handlers(new_targets, logger) + assert len(logger.handlers) == 0 + mocked_creator.assert_not_called() + + finally: + LoggingUtils.create_logging_handler = orig_create_logging_handler @pytest.mark.usefixtures("tango_context", "initialize_device") @@ -125,7 +183,7 @@ class TestSKABaseDevice(object): properties = { 'SkaLevel': '4', 'GroupDefinitions': '', - 'LoggingTargetsDefault': ['console::cout'] + 'LoggingTargetsDefault': '' } @classmethod @@ -216,6 +274,7 @@ class TestSKABaseDevice(object): for level in TangoLoggingLevel: tango_context.device.loggingLevel = level assert tango_context.device.loggingLevel == level + assert tango_context.device.get_logging_level() == level with pytest.raises(DevFailed): tango_context.device.loggingLevel = TangoLoggingLevel.FATAL + 100 @@ -226,27 +285,32 @@ class TestSKABaseDevice(object): def test_loggingTargets(self, tango_context): """Test for loggingTargets""" # PROTECTED REGION ID(SKABaseDevice.test_loggingTargets) ENABLED START # - assert tango_context.device.loggingTargets == ("console::cout",) + assert tango_context.device.loggingTargets == tuple() - with mock.patch("SKABaseDevice._create_logging_handler") as mocked_creator: + with mock.patch("SKABaseDevice.LoggingUtils.create_logging_handler") as mocked_creator: - def null_creator(target, device_name): + def null_creator(target): handler = logging.NullHandler() handler.name = target return handler mocked_creator.side_effect = null_creator - device_fqdn = tango_context.get_device_access() + + # test adding console target + tango_context.device.loggingTargets = ["console::cout"] + assert tango_context.device.loggingTargets == ("console::cout", ) + mocked_creator.assert_called_once_with("console::cout") # test adding file and syslog targets (already have console) + mocked_creator.reset_mock() tango_context.device.loggingTargets = [ "console::cout", "file::/tmp/dummy", "syslog::some/address"] assert tango_context.device.loggingTargets == ( "console::cout", "file::/tmp/dummy", "syslog::some/address") - mocked_creator.call_count == 2 + assert mocked_creator.call_count == 2 mocked_creator.assert_has_calls( - [mock.call("file::/tmp/dummy", device_fqdn), - mock.call("syslog::some/address", device_fqdn)], + [mock.call("file::/tmp/dummy"), + mock.call("syslog::some/address")], any_order=True) mocked_creator.reset_mock() @@ -317,13 +381,3 @@ class TestSKABaseDevice(object): with pytest.raises(Exception): SKABaseDevice._parse_argin( SKABaseDevice, '{"class":"SKABaseDevice"}', required=['missing']) - - # TODO: Fix this test case when "__DeviceImpl__debug_stream() missing 'msg' argument" is resolved. - # def test_dev_logging(self, tango_context): - # SKABaseDevice._central_logging_level = int(tango.LogLevel.LOG_DEBUG) - # SKABaseDevice._element_logging_level = int(tango.LogLevel.LOG_DEBUG) - # SKABaseDevice._storage_logging_level = int(tango.LogLevel.LOG_DEBUG) - # result = SKABaseDevice.dev_logging(SKABaseDevice, "test message", int(tango.LogLevel.LOG_DEBUG)) - # result = [] - # SKABaseDevice.error_stream(SKABaseDevice, "Syslog cannot be initialized") - # assert result == None diff --git a/skabase/SKACapability/SKACapability.xmi b/skabase/SKACapability/SKACapability.xmi index 3b89f5c458ef4f63dd372daca3eebe55d1efddac..434a33f05c626b0bb3801989660daa53153b55be 100644 --- a/skabase/SKACapability/SKACapability.xmi +++ b/skabase/SKACapability/SKACapability.xmi @@ -19,7 +19,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <deviceProperties name="GroupDefinitions" description="Each string in the list is a JSON serialised dict defining the ``group_name``,
``devices`` and ``subgroups`` in the group. A TANGO Group object is created
for each item in the list, according to the hierarchy defined. This provides
easy access to the managed devices in bulk, or individually.

The general format of the list is as follows, with optional ``devices`` and
``subgroups`` keys:
 [ {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ...]},
 {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ``<dev name>``, ...],
 ``subgroups`` : [{<nested group>},
 {<nested group>}, ...]},
 ...
 ]

For example, a hierarchy of racks, servers and switches:
 [ {``group_name``: ``servers``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/server/3``, ``elt/server/4``]},
 {``group_name``: ``switches``,
 ``devices``: [``elt/switch/A``, ``elt/switch/B``]},
 {``group_name``: ``pdus``,
 ``devices``: [``elt/pdu/rackA``, ``elt/pdu/rackB``]},
 {``group_name``: ``racks``,
 ``subgroups``: [
 {``group_name``: ``rackA``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/switch/A``, ``elt/pdu/rackA``]},
 {``group_name``: ``rackB``,
 ``devices``: [``elt/server/3``, ``elt/server/4``,
 ``elt/switch/B``, ``elt/pdu/rackB``],
 ``subgroups``: []}
 ]} ]"> <type xsi:type="pogoDsl:StringVectorType"/> @@ -175,7 +174,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <attributes name="usedComponents" attType="Spectrum" rwType="READ" displayLevel="OPERATOR" polledPeriod="1000" maxX="100" maxY="" allocReadMember="true" isDynamic="false"> <dataType xsi:type="pogoDsl:StringType"/> diff --git a/skabase/SKACapability/test/SKACapability_test.py b/skabase/SKACapability/test/SKACapability_test.py index cf740888f3bf4086e86f22dc11711ab7bf7167d2..1dd2f62cd81997c12d3fc61c48f0b7ca72a0c8b0 100644 --- a/skabase/SKACapability/test/SKACapability_test.py +++ b/skabase/SKACapability/test/SKACapability_test.py @@ -31,7 +31,7 @@ class TestSKACapability(object): properties = { 'SkaLevel': '4', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'GroupDefinitions': '', 'CapType': '', 'CapID': '', diff --git a/skabase/SKALogger/SKALogger.xmi b/skabase/SKALogger/SKALogger.xmi index 8cce495b1fb65ebdb20ca67d0d0c77018d3f1790..fa44b4defa3089f373368af654696d520df256a6 100644 --- a/skabase/SKALogger/SKALogger.xmi +++ b/skabase/SKALogger/SKALogger.xmi @@ -23,7 +23,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0"> <argin description="none"> @@ -118,7 +117,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKAMaster/SKAMaster.xmi b/skabase/SKAMaster/SKAMaster.xmi index f7810aa24482cb71138773ae56458fd1408fa899..5cad120e5d25eeb78102298dbd3e19b99ca04272 100644 --- a/skabase/SKAMaster/SKAMaster.xmi +++ b/skabase/SKAMaster/SKAMaster.xmi @@ -19,7 +19,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <deviceProperties name="GroupDefinitions" description="Each string in the list is a JSON serialised dict defining the ``group_name``,
``devices`` and ``subgroups`` in the group. A TANGO Group object is created
for each item in the list, according to the hierarchy defined. This provides
easy access to the managed devices in bulk, or individually.

The general format of the list is as follows, with optional ``devices`` and
``subgroups`` keys:
 [ {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ...]},
 {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ``<dev name>``, ...],
 ``subgroups`` : [{<nested group>},
 {<nested group>}, ...]},
 ...
 ]

For example, a hierarchy of racks, servers and switches:
 [ {``group_name``: ``servers``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/server/3``, ``elt/server/4``]},
 {``group_name``: ``switches``,
 ``devices``: [``elt/switch/A``, ``elt/switch/B``]},
 {``group_name``: ``pdus``,
 ``devices``: [``elt/pdu/rackA``, ``elt/pdu/rackB``]},
 {``group_name``: ``racks``,
 ``subgroups``: [
 {``group_name``: ``rackA``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/switch/A``, ``elt/pdu/rackA``]},
 {``group_name``: ``rackB``,
 ``devices``: [``elt/server/3``, ``elt/server/4``,
 ``elt/switch/B``, ``elt/pdu/rackB``],
 ``subgroups``: []}
 ]} ]"> <type xsi:type="pogoDsl:StringVectorType"/> @@ -193,7 +192,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKAMaster/test/SKAMaster_test.py b/skabase/SKAMaster/test/SKAMaster_test.py index 427f5bbadcbcda30a31eeb047b21d91032b4147b..c4fbd2e94125380f53162c6d504e909035447568 100644 --- a/skabase/SKAMaster/test/SKAMaster_test.py +++ b/skabase/SKAMaster/test/SKAMaster_test.py @@ -33,7 +33,7 @@ class TestSKAMaster(object): capabilities = ['BAND1:1', 'BAND2:1', 'BAND3:0', 'BAND4:0', 'BAND5:0'] properties = { 'SkaLevel': '4', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'GroupDefinitions': '', 'NrSubarrays': '16', 'CapabilityTypes': '', diff --git a/skabase/SKAObsDevice/SKAObsDevice.xmi b/skabase/SKAObsDevice/SKAObsDevice.xmi index 99212d435a7307f9fa052605c0ceb36537825154..1dfc16e6f36ae1c6969f469de97d4dfa11bfb5d7 100644 --- a/skabase/SKAObsDevice/SKAObsDevice.xmi +++ b/skabase/SKAObsDevice/SKAObsDevice.xmi @@ -19,7 +19,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <deviceProperties name="GroupDefinitions" description="Each string in the list is a JSON serialised dict defining the ``group_name``,
``devices`` and ``subgroups`` in the group. A TANGO Group object is created
for each item in the list, according to the hierarchy defined. This provides
easy access to the managed devices in bulk, or individually.

The general format of the list is as follows, with optional ``devices`` and
``subgroups`` keys:
 [ {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ...]},
 {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ``<dev name>``, ...],
 ``subgroups`` : [{<nested group>},
 {<nested group>}, ...]},
 ...
 ]

For example, a hierarchy of racks, servers and switches:
 [ {``group_name``: ``servers``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/server/3``, ``elt/server/4``]},
 {``group_name``: ``switches``,
 ``devices``: [``elt/switch/A``, ``elt/switch/B``]},
 {``group_name``: ``pdus``,
 ``devices``: [``elt/pdu/rackA``, ``elt/pdu/rackB``]},
 {``group_name``: ``racks``,
 ``subgroups``: [
 {``group_name``: ``rackA``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/switch/A``, ``elt/pdu/rackA``]},
 {``group_name``: ``rackB``,
 ``devices``: [``elt/server/3``, ``elt/server/4``,
 ``elt/switch/B``, ``elt/pdu/rackB``],
 ``subgroups``: []}
 ]} ]"> <type xsi:type="pogoDsl:StringVectorType"/> @@ -159,7 +158,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKAObsDevice/test/SKAObsDevice_test.py b/skabase/SKAObsDevice/test/SKAObsDevice_test.py index ff78cd957220ac0e67c42590c55605746f39d631..9bf6513e30c8fdc799e880d022c887a1a6b519c4 100644 --- a/skabase/SKAObsDevice/test/SKAObsDevice_test.py +++ b/skabase/SKAObsDevice/test/SKAObsDevice_test.py @@ -32,7 +32,7 @@ class TestSKAObsDevice(object): properties = { 'SkaLevel': '4', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'GroupDefinitions': '', } diff --git a/skabase/SKASubarray/SKASubarray.xmi b/skabase/SKASubarray/SKASubarray.xmi index 2b3b125484af721ba1a5174a09f90ae923793bb5..ed9e7e3f5cd3b8ad1575aa17180322880c2c0b68 100644 --- a/skabase/SKASubarray/SKASubarray.xmi +++ b/skabase/SKASubarray/SKASubarray.xmi @@ -18,7 +18,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <deviceProperties name="GroupDefinitions" description="Each string in the list is a JSON serialised dict defining the ``group_name``,
``devices`` and ``subgroups`` in the group. A TANGO Group object is created
for each item in the list, according to the hierarchy defined. This provides
easy access to the managed devices in bulk, or individually.

The general format of the list is as follows, with optional ``devices`` and
``subgroups`` keys:
 [ {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ...]},
 {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ``<dev name>``, ...],
 ``subgroups`` : [{<nested group>},
 {<nested group>}, ...]},
 ...
 ]

For example, a hierarchy of racks, servers and switches:
 [ {``group_name``: ``servers``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/server/3``, ``elt/server/4``]},
 {``group_name``: ``switches``,
 ``devices``: [``elt/switch/A``, ``elt/switch/B``]},
 {``group_name``: ``pdus``,
 ``devices``: [``elt/pdu/rackA``, ``elt/pdu/rackB``]},
 {``group_name``: ``racks``,
 ``subgroups``: [
 {``group_name``: ``rackA``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/switch/A``, ``elt/pdu/rackA``]},
 {``group_name``: ``rackB``,
 ``devices``: [``elt/server/3``, ``elt/server/4``,
 ``elt/switch/B``, ``elt/pdu/rackB``],
 ``subgroups``: []}
 ]} ]"> <type xsi:type="pogoDsl:StringVectorType"/> @@ -278,7 +277,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKASubarray/test/SKASubarray_test.py b/skabase/SKASubarray/test/SKASubarray_test.py index 6fb9faa4555c8ee49311c0e3b3b67e084731c8ec..d7ba73b2566dd78404c56e30d79b53b04fd094b7 100644 --- a/skabase/SKASubarray/test/SKASubarray_test.py +++ b/skabase/SKASubarray/test/SKASubarray_test.py @@ -34,7 +34,7 @@ class TestSKASubarray(object): 'CapabilityTypes': '', 'GroupDefinitions': '', 'SkaLevel': '4', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'SubID': '', } diff --git a/skabase/SKATelState/SKATelState.xmi b/skabase/SKATelState/SKATelState.xmi index af2ea16f532f71c66650cd84493ade1066924446..357aa897df1bf8e80d5c164da9c8b5bc61063a15 100644 --- a/skabase/SKATelState/SKATelState.xmi +++ b/skabase/SKATelState/SKATelState.xmi @@ -27,7 +27,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0"> <argin description="none"> @@ -113,7 +112,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKATelState/test/SKATelState_test.py b/skabase/SKATelState/test/SKATelState_test.py index 1394193ebef272cb810d71cb3b0277692eff803f..e3efe5d3578a1f45ad8cb947b0f25af53cb5cfee 100644 --- a/skabase/SKATelState/test/SKATelState_test.py +++ b/skabase/SKATelState/test/SKATelState_test.py @@ -34,7 +34,7 @@ class TestSKATelState(object): 'TelStateConfigFile': '', 'SkaLevel': '4', 'GroupDefinitions': '', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', } @classmethod diff --git a/skabase/SKATestDevice/SKATestDevice.py b/skabase/SKATestDevice/SKATestDevice.py index efec789e9bf8b71b4cd7c65fc8052a0ae9ca92fb..fb9d5e1e48f15c354bf83567a0d664cd8a89f0cb 100644 --- a/skabase/SKATestDevice/SKATestDevice.py +++ b/skabase/SKATestDevice/SKATestDevice.py @@ -187,11 +187,11 @@ class SKATestDevice(with_metaclass(DeviceMeta, SKABaseDevice)): def On(self): # PROTECTED REGION ID(SKATestDevice.On) ENABLED START # """Starts the device""" - self.dev_logging("TurnOn Sending DEBUG", int(tango.LogLevel.LOG_DEBUG)) - self.dev_logging("TurnOn Sending INFO", int(tango.LogLevel.LOG_INFO)) - self.dev_logging("TurnOn Sending WARNING", int(tango.LogLevel.LOG_WARN)) - self.dev_logging("TurnOn Sending ERROR", int(tango.LogLevel.LOG_ERROR)) - self.dev_logging("TurnOn Sending FATAL", int(tango.LogLevel.LOG_FATAL)) + self.logger.debug("TurnOn Sending DEBUG") + self.logger.info("TurnOn Sending INFO") + self.logger.warning("TurnOn Sending WARNING") + self.logger.error("TurnOn Sending ERROR") + self.logger.critical("TurnOn Sending CRITICAL") #TODO: Set state to ON # PROTECTED REGION END # // SKATestDevice.On @@ -201,11 +201,11 @@ class SKATestDevice(with_metaclass(DeviceMeta, SKABaseDevice)): def Stop(self): # PROTECTED REGION ID(SKATestDevice.Stop) ENABLED START # """Stops the device""" - self.dev_logging("TurnOFF Sending DEBUG", int(tango.LogLevel.LOG_DEBUG)) - self.dev_logging("TurnOFF Sending INFO", int(tango.LogLevel.LOG_INFO)) - self.dev_logging("TurnOFF Sending WARNING", int(tango.LogLevel.LOG_WARN)) - self.dev_logging("TurnOFF Sending ERROR", int(tango.LogLevel.LOG_ERROR)) - self.dev_logging("TurnOFF Sending FATAL", int(tango.LogLevel.LOG_FATAL)) + self.logger.debug("TurnOFF Sending DEBUG") + self.logger.info("TurnOFF Sending INFO") + self.logger.warning("TurnOFF Sending WARNING") + self.logger.error("TurnOFF Sending ERROR") + self.logger.critical("TurnOFF Sending CRITICAL") # TODO: Set state to OFF # PROTECTED REGION END # // SKATestDevice.Stop diff --git a/skabase/SKATestDevice/SKATestDevice.xmi b/skabase/SKATestDevice/SKATestDevice.xmi index 2ace4655c7a57a934f4b6c6bea8fe5de2c26406c..9b50bcae527d617af489a94e7f17f358ffa87bc0 100644 --- a/skabase/SKATestDevice/SKATestDevice.xmi +++ b/skabase/SKATestDevice/SKATestDevice.xmi @@ -19,7 +19,6 @@ <deviceProperties name="LoggingTargetsDefault" description="Default logging targets at device startup.
Each item has the format: target_type::target_name.
To log to stdout, use 'console::cout'.
To log to syslog, use 'syslog::<address>',
 where <address> is a file path,
 for example 'syslog::/var/run/rsyslog/dev/log'.
To log to a file, use 'file::<path>',
 where <path> is a file path,
 for example 'file::/tmp/my_dev.log'."> <type xsi:type="pogoDsl:StringVectorType"/> <status abstract="false" inherited="true" concrete="true"/> - <DefaultPropValue>console::cout</DefaultPropValue> </deviceProperties> <deviceProperties name="GroupDefinitions" description="Each string in the list is a JSON serialised dict defining the ``group_name``,
``devices`` and ``subgroups`` in the group. A TANGO Group object is created
for each item in the list, according to the hierarchy defined. This provides
easy access to the managed devices in bulk, or individually.

The general format of the list is as follows, with optional ``devices`` and
``subgroups`` keys:
 [ {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ...]},
 {``group_name``: ``<name>``,
 ``devices``: [``<dev name>``, ``<dev name>``, ...],
 ``subgroups`` : [{<nested group>},
 {<nested group>}, ...]},
 ...
 ]

For example, a hierarchy of racks, servers and switches:
 [ {``group_name``: ``servers``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/server/3``, ``elt/server/4``]},
 {``group_name``: ``switches``,
 ``devices``: [``elt/switch/A``, ``elt/switch/B``]},
 {``group_name``: ``pdus``,
 ``devices``: [``elt/pdu/rackA``, ``elt/pdu/rackB``]},
 {``group_name``: ``racks``,
 ``subgroups``: [
 {``group_name``: ``rackA``,
 ``devices``: [``elt/server/1``, ``elt/server/2``,
 ``elt/switch/A``, ``elt/pdu/rackA``]},
 {``group_name``: ``rackB``,
 ``devices``: [``elt/server/3``, ``elt/server/4``,
 ``elt/switch/B``, ``elt/pdu/rackB``],
 ``subgroups``: []}
 ]} ]"> <type xsi:type="pogoDsl:StringVectorType"/> @@ -177,7 +176,7 @@ <archiveEvent fire="false" libCheckCriteria="false"/> <dataReadyEvent fire="false" libCheckCriteria="true"/> <status abstract="false" inherited="true" concrete="true"/> - <properties description="Current logging targets for this device - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> + <properties description="Logging targets for this device, excluding ska_logging defaults - 
initialises to LoggingTargetsDefault on startup" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/> </attributes> <states name="ON" description="This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, th motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e. if the State is ON and one attribute has its quality factor to ATTR_WARNING or ATTR_ALARM, then the State is modified to ALARM."> <status abstract="false" inherited="true" concrete="true"/> diff --git a/skabase/SKATestDevice/test/SKATestDevice_test.py b/skabase/SKATestDevice/test/SKATestDevice_test.py index 52e60f66ec17d3783b9124bd89c429c96192bf64..97f21aebf3d729b9fea4ddb9a87bff2efff6de7e 100644 --- a/skabase/SKATestDevice/test/SKATestDevice_test.py +++ b/skabase/SKATestDevice/test/SKATestDevice_test.py @@ -33,7 +33,7 @@ class TestSKATestDevice(object): properties = { 'SkaLevel': '4', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'LoggingLevelDefault': '4', 'GroupDefinitions': '' } diff --git a/skabase/conftest.py b/skabase/conftest.py index dee4bbf35b7056228b9d0da26d7de6b9b86ee331..7be5a016aec1a2442ac2824acb3fe10133429857 100644 --- a/skabase/conftest.py +++ b/skabase/conftest.py @@ -5,11 +5,13 @@ tests. import os import time import importlib -import mock import pytest +from unittest import mock + from tango.test_context import DeviceTestContext + @pytest.fixture(scope="class") def tango_context(request): """Creates and returns a TANGO DeviceTestContext object. @@ -21,7 +23,7 @@ def tango_context(request): """ ska_master_properties = { 'SkaLevel': '4', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'GroupDefinitions': '', 'NrSubarrays': '16', 'CapabilityTypes': '', @@ -30,7 +32,7 @@ def tango_context(request): ska_subarray_properties = { 'CapabilityTypes': 'BAND1', - 'LoggingTargetsDefault': ['console::cout'], + 'LoggingTargetsDefault': '', 'GroupDefinitions': '', 'SkaLevel': '4', 'SubID': '1', diff --git a/skabase/release.py b/skabase/release.py index a98e77dbfb9abf51d74d0420e5e52dbc5581c46a..90c29bd5913fd3daaf0c485875f7e8ef0a30759e 100644 --- a/skabase/release.py +++ b/skabase/release.py @@ -7,7 +7,7 @@ """Release information for lmc-base-classes Python Package""" name = """lmcbaseclasses""" -version = "0.2.0" +version = "0.3.1" version_info = version.split(".") description = """A set of generic base devices for SKA Telescope.""" author = "SKA India and SARAO"