diff --git a/Pipfile b/Pipfile index 66f5aa3..39487d4 100644 --- a/Pipfile +++ b/Pipfile @@ -13,7 +13,9 @@ nose = "==1.3.7" nose-cov = "==1.6" mock = "==2.0.0" "autopep8" = "==1.3.1" -pylint = "==1.8.2" +pylint = "*" +mypy = "*" +flake8 = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 6e64433..8d34ab8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,255 +1,301 @@ -{ - "_meta": { - "hash": { - "sha256": "82246484e6e699342d91710f9f1f26153fd58f2fe2346d7f26cb3295267892ef" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "lxml": { - "hashes": [ - "sha256:0ee07da52d240f1dc3c83eef5cd5f1b7f018226c1121f2a54d446645779a6d17", - "sha256:155521c337acecf8202091cff85bb9f709f238130ebadf04280fb1db11f5ad8b", - "sha256:155c916cf2645b4a8f2bd5d09065e92d1b67b8d464bdc001e0b524af84bedf6f", - "sha256:2190266059fec3c5a55f9d6c30532c64c6d414d3228909c0af573fe4907e78d1", - "sha256:29a36e354c39b2e24bc4ee103de53417ebb80f976a6ab9e8d093d559e2ac03e1", - "sha256:2dedfeeecc2d5a939cf622602f5a1ce443ca82407f386880f739f1a9f08053ad", - "sha256:3b33549fb8f91b38a7500078242b03cca513f3412a2cdae722e89bf83f95971d", - "sha256:4187c4b0cefc3353181db048c51f42c489d9ac51e40b86c4851dc0671372971d", - "sha256:41f59cbdab232f11680d5d4dec9f2e6782fd24d78e37ee833447702e34e675f4", - "sha256:470d7ce41e8047208ba1a376560bad17f1468df1f3097bc83902b26cfafdbb0c", - "sha256:49a655956f8de69e1258bc0fcfc43eb3bd1e038655784d77d1869b4b81444e37", - "sha256:4c12e90886d9c53ab434c8d0cebea122321cce19614c3c6b6d1a7700d7cc6212", - "sha256:6cba398eb37e0631e60e0e080c101cfe91769b2c8267105b64b4625e2581ea21", - "sha256:79322000279cda10b53c374d53ca632ead3bc51c6aebf8e62c8fa93a4d08b750", - "sha256:87a66bcadac270fc010cb029022a93fc722bf1204a8b03e782d4c790f0edf7ca", - "sha256:940caef1ec7c78e0c34b0f6b94fe42d0f2022915ffc78643d28538a5cfd0f40e", - "sha256:950e63387514aa1b881eba5ac6cb2ec51a118b3dafe99dd80ca19d8fb0142f30", - "sha256:af8a5373241d09b8fc53e0490e1719ce5dc90a21b19db89b6596c1adcdd52270", - "sha256:b106d4d2383382399ad82108fd187e92f40b1c90f55c2d36bbcb1c44bcf940fc", - "sha256:ba05732e4bcf59e948f61588851dcf620fd60d5bbd9d704203e5f59bbaa60219", - "sha256:d2c985d2460b81c6ca5feb8b86f1bc594ad59405d0bdf68626b85852b701553c", - "sha256:d5d29663e979e83b3fc361e97200f959cddb3a14797391d15273d84a5a8ae44b", - "sha256:dd291debfaa535d9cb6cee8d7aca2328775e037d02d13f1634e57f49bc302cc4", - "sha256:e37427d5a27eefbcfc48847e0b37f348113fac7280bc857421db39ffc6372570", - "sha256:e608839a5ee2180164424ccf279c8e2d9bbe8816d002c58fd97d6b621ba4aa94", - "sha256:e6b6698415c7e8d227a47a3b1038e1b37c2b438a1b48c2db7ad9e74ddbcd1149", - "sha256:e7e41d383f19bab9d57f5f3b18d158655bcd682e7e723f441b9e183e1e35a6b5", - "sha256:fa7320679ced5e25b20203d157280680fc84eb783b6cc650cb0c98e1858b7dd3" - ], - "index": "pypi", - "version": "==4.1.1" - }, - "rx": { - "hashes": [ - "sha256:a9f15e78dedc45aa4143439bbce64a9828cee1b822ed2ccd22ae7ed8c9c2c738", - "sha256:f09c9f6cd54f368174738408af899756a777655baaac605353ad64c2766d3e70" - ], - "index": "pypi", - "version": "==1.5.9" - }, - "six": { - "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - ], - "version": "==1.11.0" - }, - "websocket-client": { - "hashes": [ - "sha256:40ac14a0c54e14d22809a5c8d553de5a2ae45de3c60105fae53bcb281b3fe6fb" - ], - "index": "pypi", - "version": "==0.40.0" - } - }, - "develop": { - "astroid": { - "hashes": [ - "sha256:a92c1197dd496ef2470e73e1c296fc02a719907ee07259744e26a13bda9d4862", - "sha256:b76e5109ff0f386dd229673ca1323d21b1e9bb9c38eaed2cf830882dd7628be2" - ], - "version": "==1.6.2" - }, - "autopep8": { - "hashes": [ - "sha256:405eaa5199c74a0570125916e6af44d39343c1fcaaafee117fc329a3f86048c4" - ], - "index": "pypi", - "version": "==1.3.1" - }, - "colorama": { - "hashes": [ - "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", - "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" - ], - "markers": "sys_platform == 'win32'", - "version": "==0.3.9" - }, - "cov-core": { - "hashes": [ - "sha256:4a14c67d520fda9d42b0da6134638578caae1d374b9bb462d8de00587dba764c" - ], - "version": "==1.15.0" - }, - "coverage": { - "hashes": [ - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", - "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", - "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", - "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", - "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" - ], - "version": "==4.5.1" - }, - "isort": { - "hashes": [ - "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", - "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", - "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" - ], - "version": "==4.3.4" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", - "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", - "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", - "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", - "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", - "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", - "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", - "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", - "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", - "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", - "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", - "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", - "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", - "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", - "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", - "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", - "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", - "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", - "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", - "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", - "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", - "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", - "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", - "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", - "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", - "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", - "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", - "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", - "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" - ], - "version": "==1.3.1" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "mock": { - "hashes": [ - "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", - "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" - ], - "index": "pypi", - "version": "==2.0.0" - }, - "nose": { - "hashes": [ - "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", - "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", - "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" - ], - "index": "pypi", - "version": "==1.3.7" - }, - "nose-cov": { - "hashes": [ - "sha256:8bec0335598f1cc69e3262cc50d7678c1a6010fa44625ce343c4ec1500774412" - ], - "index": "pypi", - "version": "==1.6" - }, - "pbr": { - "hashes": [ - "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1", - "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac" - ], - "version": "==3.1.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" - ], - "version": "==2.3.1" - }, - "pylint": { - "hashes": [ - "sha256:156839bedaa798febee72893beef00c650c2e7abafb5586fc7a6a56be7f80412", - "sha256:4fe3b99da7e789545327b75548cee6b511e4faa98afe268130fea1af4b5ec022" - ], - "index": "pypi", - "version": "==1.8.2" - }, - "six": { - "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - ], - "version": "==1.11.0" - }, - "wrapt": { - "hashes": [ - "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" - ], - "version": "==1.10.11" - } - } -} +{ + "_meta": { + "hash": { + "sha256": "6517f1efaf74f908493f0b61b82f474e239bcc9a64d9f6b2fec4083c3bf05ecf" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "lxml": { + "hashes": [ + "sha256:0ee07da52d240f1dc3c83eef5cd5f1b7f018226c1121f2a54d446645779a6d17", + "sha256:155521c337acecf8202091cff85bb9f709f238130ebadf04280fb1db11f5ad8b", + "sha256:155c916cf2645b4a8f2bd5d09065e92d1b67b8d464bdc001e0b524af84bedf6f", + "sha256:2190266059fec3c5a55f9d6c30532c64c6d414d3228909c0af573fe4907e78d1", + "sha256:29a36e354c39b2e24bc4ee103de53417ebb80f976a6ab9e8d093d559e2ac03e1", + "sha256:2dedfeeecc2d5a939cf622602f5a1ce443ca82407f386880f739f1a9f08053ad", + "sha256:3b33549fb8f91b38a7500078242b03cca513f3412a2cdae722e89bf83f95971d", + "sha256:4187c4b0cefc3353181db048c51f42c489d9ac51e40b86c4851dc0671372971d", + "sha256:41f59cbdab232f11680d5d4dec9f2e6782fd24d78e37ee833447702e34e675f4", + "sha256:470d7ce41e8047208ba1a376560bad17f1468df1f3097bc83902b26cfafdbb0c", + "sha256:49a655956f8de69e1258bc0fcfc43eb3bd1e038655784d77d1869b4b81444e37", + "sha256:4c12e90886d9c53ab434c8d0cebea122321cce19614c3c6b6d1a7700d7cc6212", + "sha256:6cba398eb37e0631e60e0e080c101cfe91769b2c8267105b64b4625e2581ea21", + "sha256:79322000279cda10b53c374d53ca632ead3bc51c6aebf8e62c8fa93a4d08b750", + "sha256:87a66bcadac270fc010cb029022a93fc722bf1204a8b03e782d4c790f0edf7ca", + "sha256:940caef1ec7c78e0c34b0f6b94fe42d0f2022915ffc78643d28538a5cfd0f40e", + "sha256:950e63387514aa1b881eba5ac6cb2ec51a118b3dafe99dd80ca19d8fb0142f30", + "sha256:af8a5373241d09b8fc53e0490e1719ce5dc90a21b19db89b6596c1adcdd52270", + "sha256:b106d4d2383382399ad82108fd187e92f40b1c90f55c2d36bbcb1c44bcf940fc", + "sha256:ba05732e4bcf59e948f61588851dcf620fd60d5bbd9d704203e5f59bbaa60219", + "sha256:d2c985d2460b81c6ca5feb8b86f1bc594ad59405d0bdf68626b85852b701553c", + "sha256:d5d29663e979e83b3fc361e97200f959cddb3a14797391d15273d84a5a8ae44b", + "sha256:dd291debfaa535d9cb6cee8d7aca2328775e037d02d13f1634e57f49bc302cc4", + "sha256:e37427d5a27eefbcfc48847e0b37f348113fac7280bc857421db39ffc6372570", + "sha256:e608839a5ee2180164424ccf279c8e2d9bbe8816d002c58fd97d6b621ba4aa94", + "sha256:e6b6698415c7e8d227a47a3b1038e1b37c2b438a1b48c2db7ad9e74ddbcd1149", + "sha256:e7e41d383f19bab9d57f5f3b18d158655bcd682e7e723f441b9e183e1e35a6b5", + "sha256:fa7320679ced5e25b20203d157280680fc84eb783b6cc650cb0c98e1858b7dd3" + ], + "index": "pypi", + "version": "==4.1.1" + }, + "rx": { + "hashes": [ + "sha256:a9f15e78dedc45aa4143439bbce64a9828cee1b822ed2ccd22ae7ed8c9c2c738", + "sha256:f09c9f6cd54f368174738408af899756a777655baaac605353ad64c2766d3e70" + ], + "index": "pypi", + "version": "==1.5.9" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "websocket-client": { + "hashes": [ + "sha256:40ac14a0c54e14d22809a5c8d553de5a2ae45de3c60105fae53bcb281b3fe6fb" + ], + "index": "pypi", + "version": "==0.40.0" + } + }, + "develop": { + "astroid": { + "hashes": [ + "sha256:a92c1197dd496ef2470e73e1c296fc02a719907ee07259744e26a13bda9d4862", + "sha256:b76e5109ff0f386dd229673ca1323d21b1e9bb9c38eaed2cf830882dd7628be2" + ], + "version": "==1.6.2" + }, + "autopep8": { + "hashes": [ + "sha256:405eaa5199c74a0570125916e6af44d39343c1fcaaafee117fc329a3f86048c4" + ], + "index": "pypi", + "version": "==1.3.1" + }, + "colorama": { + "hashes": [ + "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", + "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.3.9" + }, + "cov-core": { + "hashes": [ + "sha256:4a14c67d520fda9d42b0da6134638578caae1d374b9bb462d8de00587dba764c" + ], + "version": "==1.15.0" + }, + "coverage": { + "hashes": [ + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", + "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", + "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", + "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", + "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", + "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" + ], + "version": "==4.5.1" + }, + "flake8": { + "hashes": [ + "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", + "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" + ], + "index": "pypi", + "version": "==3.5.0" + }, + "isort": { + "hashes": [ + "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", + "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", + "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" + ], + "version": "==4.3.4" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", + "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", + "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", + "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", + "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", + "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", + "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", + "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", + "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", + "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", + "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", + "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", + "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", + "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", + "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", + "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", + "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", + "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", + "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", + "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", + "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", + "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", + "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", + "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", + "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", + "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", + "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", + "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", + "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" + ], + "version": "==1.3.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "mock": { + "hashes": [ + "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", + "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "mypy": { + "hashes": [ + "sha256:3bd95a1369810f7693366911d85be9f0a0bd994f6cb7162b7a994e5ded90e3d9", + "sha256:7247f9948d7cdaae9408a4ee1662a01853c24e668117b4419acf025b05fbe3ce" + ], + "index": "pypi", + "version": "==0.580" + }, + "nose": { + "hashes": [ + "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", + "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", + "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" + ], + "index": "pypi", + "version": "==1.3.7" + }, + "nose-cov": { + "hashes": [ + "sha256:8bec0335598f1cc69e3262cc50d7678c1a6010fa44625ce343c4ec1500774412" + ], + "index": "pypi", + "version": "==1.6" + }, + "pbr": { + "hashes": [ + "sha256:56b7a8ba7d64bf6135a9dfefb85a80d95924b3fde5ed6343a1a1d464a040dae3", + "sha256:de75cf1d510542c746beeff66b52241eb12c8f95f2ef846ee50ed5d72392caa4" + ], + "version": "==4.0.1" + }, + "pycodestyle": { + "hashes": [ + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" + ], + "version": "==2.3.1" + }, + "pyflakes": { + "hashes": [ + "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", + "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" + ], + "version": "==1.6.0" + }, + "pylint": { + "hashes": [ + "sha256:34ab1a62fbdd48059d082f5a52b7e719a39b757a53ecbf0b2b7169b9c6a2cc28", + "sha256:c77311859e0c2d7932095f30d2b1bfdc4b6fe111f534450ba727a52eae330ef2" + ], + "index": "pypi", + "version": "==1.8.3" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "typed-ast": { + "hashes": [ + "sha256:0948004fa228ae071054f5208840a1e88747a357ec1101c17217bfe99b299d58", + "sha256:25d8feefe27eb0303b73545416b13d108c6067b846b543738a25ff304824ed9a", + "sha256:29464a177d56e4e055b5f7b629935af7f49c196be47528cc94e0a7bf83fbc2b9", + "sha256:2e214b72168ea0275efd6c884b114ab42e316de3ffa125b267e732ed2abda892", + "sha256:3e0d5e48e3a23e9a4d1a9f698e32a542a4a288c871d33ed8df1b092a40f3a0f9", + "sha256:519425deca5c2b2bdac49f77b2c5625781abbaf9a809d727d3a5596b30bb4ded", + "sha256:57fe287f0cdd9ceaf69e7b71a2e94a24b5d268b35df251a88fef5cc241bf73aa", + "sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe", + "sha256:68ba70684990f59497680ff90d18e756a47bf4863c604098f10de9716b2c0bdd", + "sha256:6de012d2b166fe7a4cdf505eee3aaa12192f7ba365beeefaca4ec10e31241a85", + "sha256:79b91ebe5a28d349b6d0d323023350133e927b4de5b651a8aa2db69c761420c6", + "sha256:8550177fa5d4c1f09b5e5f524411c44633c80ec69b24e0e98906dd761941ca46", + "sha256:a8034021801bc0440f2e027c354b4eafd95891b573e12ff0418dec385c76785c", + "sha256:bc978ac17468fe868ee589c795d06777f75496b1ed576d308002c8a5756fb9ea", + "sha256:c05b41bc1deade9f90ddc5d988fe506208019ebba9f2578c622516fd201f5863", + "sha256:c9b060bd1e5a26ab6e8267fd46fc9e02b54eb15fffb16d112d4c7b1c12987559", + "sha256:edb04bdd45bfd76c8292c4d9654568efaedf76fe78eb246dde69bdb13b2dad87", + "sha256:f19f2a4f547505fe9072e15f6f4ae714af51b5a681a97f187971f50c283193b6" + ], + "version": "==1.1.0" + }, + "wrapt": { + "hashes": [ + "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" + ], + "version": "==1.10.11" + } + } +} diff --git a/build.bat b/build.bat index 9f605c6..0d2ab8c 100644 --- a/build.bat +++ b/build.bat @@ -1,12 +1,7 @@ cd reactivexcomponent -echo Running lint... -pylint reactivexcomponent --extension-pkg-whitelist=lxml -f parseable > pylint.out -IF ERRORLEVEL 1 ( - echo Lint failed! - type pylint.out - EXIT /B 1 -) +echo Running flake8... +flake8 reactivexcomponent > flake8.out echo Running tests... nosetests tests/unit --with-xunit --with-cov --cov reactivexcomponent --exe diff --git a/build.sh b/build.sh index 19dfe04..1ad1b76 100755 --- a/build.sh +++ b/build.sh @@ -2,11 +2,11 @@ cd reactivexcomponent echo Running lint... -pylint reactivexcomponent --extension-pkg-whitelist=lxml -f parseable > pylint.out +flake8 reactivexcomponent > flake8.out rc=$?; if [[ $rc != 0 ]]; then - echo Lint failed! - cat pylint.out + echo Flake8 failed! + cat flake8.out exit $rc; fi diff --git a/reactivexcomponent/.flake8 b/reactivexcomponent/.flake8 new file mode 100644 index 0000000..66575ea --- /dev/null +++ b/reactivexcomponent/.flake8 @@ -0,0 +1,3 @@ +[flake8] + +max-line-length=160 diff --git a/reactivexcomponent/flake8.out b/reactivexcomponent/flake8.out new file mode 100644 index 0000000..e69de29 diff --git a/reactivexcomponent/reactivexcomponent/__init__.py b/reactivexcomponent/reactivexcomponent/__init__.py index 8968025..2add8c4 100644 --- a/reactivexcomponent/reactivexcomponent/__init__.py +++ b/reactivexcomponent/reactivexcomponent/__init__.py @@ -1,14 +1,13 @@ import logging - -from reactivexcomponent.xcomponent_api import XcomponentAPI +from typing import Any __version__ = '1.0.0' try: - from logging import NullHandler + from logging import NullHandler # type: ignore except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): + class NullHandler(logging.Handler): # type: ignore + def emit(self, record: Any) -> None: pass logging.getLogger(__name__).addHandler(NullHandler()) diff --git a/reactivexcomponent/reactivexcomponent/communication/publisher.py b/reactivexcomponent/reactivexcomponent/communication/publisher.py index 25dd647..b263ced 100644 --- a/reactivexcomponent/reactivexcomponent/communication/publisher.py +++ b/reactivexcomponent/reactivexcomponent/communication/publisher.py @@ -1,15 +1,16 @@ import json +from typing import Dict, Any +import websocket as WebSocket from reactivexcomponent.configuration.serializer import to_websocket_input_format -from reactivexcomponent.configuration.api_configuration import format_fsharp_field +from reactivexcomponent.configuration.api_configuration import format_fsharp_field, APIConfiguration class Publisher: - - def __init__(self, apiconfiguration, websocket_instance): + def __init__(self, apiconfiguration: APIConfiguration, websocket_instance: WebSocket.WebSocketApp) -> None: self.configuration = apiconfiguration self.websocket = websocket_instance - def _header_config(self, component_code, state_machine_code, message_type): + def _header_config(self, component_code: int, state_machine_code: int, message_type: str) -> Dict[str, Any]: return {"StateMachineCode": format_fsharp_field(state_machine_code), "ComponentCode": format_fsharp_field(component_code), "EventCode": self.configuration.get_publisher_details(component_code, @@ -18,36 +19,50 @@ def _header_config(self, component_code, state_machine_code, message_type): "IncomingType": 0, "MessageType": format_fsharp_field(message_type)} - def _get_routing_key(self, component_code, state_machine_code, message_type): + def _get_routing_key(self, component_code: int, state_machine_code: int, message_type: str) -> str: publisher_details = self.configuration.get_publisher_details( component_code, state_machine_code, message_type) return publisher_details['routingKey'] - def _data_to_send(self, component_name, state_machine_name, message_type, json_message): + def _data_to_send(self, component_name: str, + state_machine_name: str, message_type: str, json_message: Dict[str, Any]) -> Dict[str, Any]: component_code = self.configuration.get_component_code(component_name) state_machine_code = self.configuration.get_state_machine_code( component_name, state_machine_name) - header_config = self._header_config(component_code, state_machine_code, message_type) - routing_key = self._get_routing_key(component_code, state_machine_code, message_type) + header_config = self._header_config( + component_code, state_machine_code, message_type) + routing_key = self._get_routing_key( + component_code, state_machine_code, message_type) return { "RoutingKey": routing_key, "ComponentCode": component_code, "Event": {"Header": header_config, "JsonMessage": json.dumps(json_message)} } - def can_publish(self, component_name, state_machine_name, message_type): + def can_publish(self, component_name: str, state_machine_name: str, message_type: str) -> bool: if self.configuration.contains_state_machine(component_name, state_machine_name): - component_code = self.configuration.get_component_code(component_name) - state_machine_code = self.configuration.get_state_machine_code(component_name, state_machine_name) + component_code = self.configuration.get_component_code( + component_name) + state_machine_code = self.configuration.get_state_machine_code( + component_name, state_machine_name) return self.configuration.contains_publisher(component_code, state_machine_code, message_type) return False - def send_message(self, component_name, state_machine_name, message_type, json_message): + def send_message(self, + component_name: str, + state_machine_name: str, + message_type: str, + json_message: Dict[str, Any]) -> None: data = self._data_to_send( component_name, state_machine_name, message_type, json_message) self.websocket.send(to_websocket_input_format(data)) - - def _header_config_ref(self, component_code, state_machine_code, message_type, state_machine_id, agent_id): + + def _header_config_ref(self, + component_code: int, + state_machine_code: int, + message_type: str, + state_machine_id: int, + agent_id: int) -> Dict[str, Any]: return { "StateMachineId": format_fsharp_field(state_machine_id), "AgentId": format_fsharp_field(agent_id), @@ -58,14 +73,17 @@ def _header_config_ref(self, component_code, state_machine_code, message_type, s message_type)["eventCode"], "IncomingType": 0, "MessageType": format_fsharp_field(message_type) - } + } - def _data_to_send_with_state_machine_ref(self, state_machine_ref, message_type, json_message): + def _data_to_send_with_state_machine_ref(self, state_machine_ref: Dict[str, Any], + message_type: str, + json_message: Dict[str, Any]) -> Dict[str, Any]: component_code = state_machine_ref["ComponentCode"] state_machine_code = state_machine_ref["StateMachineCode"] header_config = self._header_config_ref(component_code, state_machine_code, message_type, state_machine_ref["StateMachineId"], state_machine_ref["AgentId"]) - routing_key = self._get_routing_key(component_code, state_machine_code, message_type) + routing_key = self._get_routing_key( + component_code, state_machine_code, message_type) return { "RoutingKey": routing_key, "ComponentCode": component_code, @@ -75,6 +93,10 @@ def _data_to_send_with_state_machine_ref(self, state_machine_ref, message_type, } } - def send_with_state_machine_ref(self, state_machine_ref, message_type, json_message): - data = self._data_to_send_with_state_machine_ref(state_machine_ref, message_type, json_message) + def send_with_state_machine_ref(self, + state_machine_ref: Dict[str, Any], + message_type: str, + json_message: Dict[str, Any]) -> None: + data = self._data_to_send_with_state_machine_ref( + state_machine_ref, message_type, json_message) self.websocket.send(to_websocket_input_format(data)) diff --git a/reactivexcomponent/reactivexcomponent/communication/subscriber.py b/reactivexcomponent/reactivexcomponent/communication/subscriber.py index 89899ed..e6dc8c9 100644 --- a/reactivexcomponent/reactivexcomponent/communication/subscriber.py +++ b/reactivexcomponent/reactivexcomponent/communication/subscriber.py @@ -1,41 +1,45 @@ import json +import websocket as WebSocket +from rx.subjects import Subject +from rx import Observable +from typing import Dict, Any, List +from reactivexcomponent.communication.publisher import Publisher from reactivexcomponent.configuration.websocket_bridge_configuration import EventType, Command, WebsocketTopicKind -from reactivexcomponent.configuration.serializer import command_data_websocket_format, get_header_with_incoming_type -from reactivexcomponent.configuration.serializer import deserialize, get_json_data +from reactivexcomponent.configuration.serializer import command_data_websocket_format, get_header_with_incoming_type, deserialize, get_json_data +from reactivexcomponent.configuration.api_configuration import APIConfiguration -def get_data_to_send(topic, kind): +def get_data_to_send(topic: str, kind: int) -> Dict[str, Any]: return {"Header": get_header_with_incoming_type(), "JsonMessage": json.dumps({ "Topic": {"Key": topic, "Kind": kind} }) - } + } + + +def is_same_state_machine(json_data: Dict[str, Any], state_machine_code: int) -> bool: + return json_data["stateMachineRef"]["StateMachineCode"] == state_machine_code -def is_same_state_machine(json_data, state_machine_code): - same_state_machine = (json_data["stateMachineRef"]["StateMachineCode"] == state_machine_code) - return same_state_machine -def is_same_component(json_data, component_code): - same_component = (json_data["stateMachineRef"]["ComponentCode"] == component_code) - return same_component +def is_same_component(json_data: Dict[str, Any], component_code: int) -> bool: + return json_data["stateMachineRef"]["ComponentCode"] == component_code -def is_subscribed(subscribed_state_machines, component_name, state_machine_name): - subscribed = (component_name in subscribed_state_machines) and ( + +def is_subscribed(subscribed_state_machines: List[str], component_name: str, state_machine_name: str) -> bool: + return (component_name in subscribed_state_machines) and ( state_machine_name in subscribed_state_machines[component_name]) - return subscribed class Subscriber: - - def __init__(self, apiconfiguration, websocket_instance, subject, reply_publisher): + def __init__(self, apiconfiguration: APIConfiguration, websocket_instance: WebSocket.WebSocketApp, subject: Subject, reply_publisher: Publisher) -> None: self.configuration = apiconfiguration self.websocket = websocket_instance - self.subscribed_state_machines = {} + self.subscribed_state_machines: List[str] = {} self.subject = subject - self.observable_subscribers = [] + self.observable_subscribers: List[Subject] = [] self.reply_publisher = reply_publisher - def _json_data_from_event(self, data): + def _json_data_from_event(self, data: Dict[str, Any]) -> Dict[str, Any]: json_data = get_json_data(data) component_code = json_data["Header"]["ComponentCode"]["Fields"][0] state_machine_code = json_data["Header"]["StateMachineCode"]["Fields"][0] @@ -54,30 +58,30 @@ def _json_data_from_event(self, data): "jsonMessage": json.loads(json_data["JsonMessage"]) } - def _prepare_state_machine_updates(self, component_name, state_machine_name): + def _prepare_state_machine_updates(self, component_name: str, state_machine_name: str) -> Subject: component_code = self.configuration.get_component_code(component_name) state_machine_code = self.configuration.get_state_machine_code( component_name, state_machine_name) filtered_observable = self.subject.map(deserialize) \ - .filter(lambda data: data["command"] == Command.update) \ - .map(lambda data: self._json_data_from_event(data["stringData"])) \ - .filter(lambda json_data: is_same_component(json_data, component_code) and - is_same_state_machine(json_data, state_machine_code)) + .filter(lambda data: data["command"] == Command.update) \ + .map(lambda data: self._json_data_from_event(data["stringData"])) \ + .filter(lambda json_data: is_same_component(json_data, component_code) and + is_same_state_machine(json_data, state_machine_code)) return filtered_observable - def _add_subscribe_state_machine(self, component_name, state_machine_name): + def _add_subscribe_state_machine(self, component_name: str, state_machine_name: str) -> None: self.subscribed_state_machines[component_name] = ( self.subscribed_state_machines).get(component_name, []) (self.subscribed_state_machines[component_name]).append( state_machine_name) - def _send_subscribe_request_to_topic(self, topic, kind): + def _send_subscribe_request_to_topic(self, topic: str, kind: int) -> None: data = get_data_to_send(topic, kind) command_data = {"Command": Command.subscribe, "Data": data} input_data = command_data_websocket_format(command_data) self.websocket.send(input_data) - def _send_subscribe_request(self, component_name, state_machine_name): + def _send_subscribe_request(self, component_name: str, state_machine_name: str) -> None: component_code = self.configuration.get_component_code(component_name) state_machine_code = self.configuration.get_state_machine_code( component_name, state_machine_name) @@ -86,36 +90,42 @@ def _send_subscribe_request(self, component_name, state_machine_name): self._send_subscribe_request_to_topic(topic, WebsocketTopicKind.Public) self._add_subscribe_state_machine(component_name, state_machine_name) - def can_subscribe(self, component_name, state_machine_name): + def can_subscribe(self, component_name: str, state_machine_name: str) -> bool: component_code = self.configuration.get_component_code(component_name) - state_machine_code = self.configuration.get_state_machine_code(component_name, state_machine_name) + state_machine_code = self.configuration.get_state_machine_code( + component_name, state_machine_name) return self.configuration.contains_subscriber(component_code, state_machine_code, EventType.Update) - def get_state_machine_updates(self, component_name, state_machine_name): - filtered_observable = self._prepare_state_machine_updates(component_name, state_machine_name) + def get_state_machine_updates(self, component_name: str, state_machine_name: str) -> Subject: + filtered_observable = self._prepare_state_machine_updates( + component_name, state_machine_name) self._send_subscribe_request(component_name, state_machine_name) return filtered_observable - def subscribe(self, component_name, state_machine_name, state_machine_update_listener): + def subscribe(self, component_name: str, state_machine_name: str, state_machine_update_listener: Observable) -> None: observable_subscriber = self._prepare_state_machine_updates(component_name, state_machine_name)\ .subscribe(state_machine_update_listener) self.observable_subscribers.append(observable_subscriber) self._send_subscribe_request(component_name, state_machine_name) - def dispose_observable_subscribers(self): + def dispose_observable_subscribers(self) -> None: for i in range(len(self.observable_subscribers)): self.observable_subscribers[i].dispose() self.observable_subscribers = [] - def remove_subscribed_statemachine(self, component_name, state_machine_name): - index = self.subscribed_state_machines[component_name].index(state_machine_name) + def remove_subscribed_statemachine(self, component_name: str, state_machine_name: str) -> None: + index = self.subscribed_state_machines[component_name].index( + state_machine_name) del self.subscribed_state_machines[component_name][index] - def unsubscribe(self, component_name, state_machine_name): + def unsubscribe(self, component_name: str, state_machine_name: str) -> None: if is_subscribed(self.subscribed_state_machines, component_name, state_machine_name): - component_code = self.configuration.get_component_code(component_name) - state_machine_code = self.configuration.get_state_machine_code(component_name, state_machine_name) - topic = self.configuration.get_subscriber_topic(component_code, state_machine_code, EventType.Update) + component_code = self.configuration.get_component_code( + component_name) + state_machine_code = self.configuration.get_state_machine_code( + component_name, state_machine_name) + topic = self.configuration.get_subscriber_topic( + component_code, state_machine_code, EventType.Update) kind = WebsocketTopicKind.Public data = get_data_to_send(topic, kind) command_data = { @@ -123,4 +133,5 @@ def unsubscribe(self, component_name, state_machine_name): "Data": data } self.websocket.send(command_data_websocket_format(command_data)) - self.remove_subscribed_statemachine(component_name, state_machine_name) + self.remove_subscribed_statemachine( + component_name, state_machine_name) diff --git a/reactivexcomponent/reactivexcomponent/communication/xc_connection.py b/reactivexcomponent/reactivexcomponent/communication/xc_connection.py index 4f0147e..890ce99 100644 --- a/reactivexcomponent/reactivexcomponent/communication/xc_connection.py +++ b/reactivexcomponent/reactivexcomponent/communication/xc_connection.py @@ -1,11 +1,11 @@ +from typing import Callable, Any from reactivexcomponent.communication.xc_session import XcSession class XcConnection: + def __init__(self) -> None: + pass - def __init__(self): - self.session = None - - def create_connection(self, xc_api, server_url, callback): - self.session = XcSession() + def create_connection(self, xc_api: str, server_url: str, callback: Callable[[Any, Any], None]) -> None: + self.session: XcSession = XcSession() self.session.init(xc_api, server_url, callback) diff --git a/reactivexcomponent/reactivexcomponent/communication/xc_session.py b/reactivexcomponent/reactivexcomponent/communication/xc_session.py index 9ae46c0..8c02aa3 100644 --- a/reactivexcomponent/reactivexcomponent/communication/xc_session.py +++ b/reactivexcomponent/reactivexcomponent/communication/xc_session.py @@ -1,48 +1,45 @@ import ssl import websocket as WebSocket +from typing import List, Any, Callable from rx.subjects import Subject from reactivexcomponent.communication.publisher import Publisher from reactivexcomponent.communication.subscriber import Subscriber from reactivexcomponent.configuration.api_configuration import APIConfiguration -def remove_element(table, element): +def remove_element(table: List[Any], element: Any) -> List[Any]: if element in table: del table[table.index(element)] else: raise Exception("Element to remove not found") + SUCCESS = None class XcSession: - - def __init__(self): - self.websocket = None - self.xc_api = "" - self.configuration = None - self.reply_publisher = None + def __init__(self) -> None: self.stream = Subject() - self.publishers = [] - self.subscribers = [] - - def init(self, xc_api, server_url, callback): + self.publishers: List[Publisher] = [] + self.subscribers: List[Subscriber] = [] + + def init(self, xc_api: str, server_url: str, callback: Callable[[Any, Any], None]) -> None: self.xc_api = xc_api - self.websocket = WebSocket.WebSocketApp(server_url) + self.websocket: WebSocket.WebSocketApp = WebSocket.WebSocketApp(server_url) self.configuration = APIConfiguration(self.xc_api) self.reply_publisher = Publisher(self.configuration, self.websocket) self.publishers.append(self.reply_publisher) - def on_message(_websocket, message): + def on_message(_websocket: WebSocket.WebSocketApp, message: str) -> None: self.stream.on_next(message) - def on_open(_websocket): + def on_open(_websocket: WebSocket.WebSocketApp) -> None: callback(SUCCESS, self) - def on_error(_websocket, error): + def on_error(_websocket: WebSocket.WebSocketApp, error: str) -> None: callback(error, None) - def on_close(_websocket): + def on_close(_websocket: WebSocket.WebSocketApp) -> None: print('### session %s closed ###' % server_url) self.websocket.on_message = on_message @@ -52,28 +49,33 @@ def on_close(_websocket): self.websocket.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) - def create_publisher(self): + def create_publisher(self) -> Publisher: configuration = APIConfiguration(self.xc_api) publisher = Publisher(configuration, self.websocket) self.publishers.append(publisher) return publisher - def create_subscriber(self): + def create_subscriber(self) -> Subscriber: configuration = APIConfiguration(self.xc_api) - subscriber = Subscriber(configuration, self.websocket, self.stream, self.reply_publisher) + subscriber = Subscriber( + configuration, self.websocket, self.stream, self.reply_publisher) self.subscribers.append(subscriber) return subscriber - def dispose_subscriber(self, subscriber): + def dispose_subscriber(self, subscriber: Subscriber) -> None: remove_element(self.subscribers, subscriber) - def dispose_publisher(self, publisher): + def dispose_publisher(self, publisher: Publisher) -> None: remove_element(self.publishers, publisher) - def dispose_publishers_subscribers(self): - self.publishers = [self.dispose_publisher(publisher) for publisher in self.publishers] - self.subscribers = [self.dispose_subscriber(subscriber) for subscriber in self.subscribers] + def dispose_publishers_subscribers(self) -> None: + for publisher in self.publishers: + self.dispose_publisher(publisher) + for subscriber in self.subscribers: + self.dispose_subscriber(subscriber) + self.publishers = [] + self.subscribers = [] - def close_session(self): + def close_session(self) -> None: self.dispose_publishers_subscribers() self.websocket.close() diff --git a/reactivexcomponent/reactivexcomponent/configuration/api_configuration.py b/reactivexcomponent/reactivexcomponent/configuration/api_configuration.py index 13a2144..fd10071 100644 --- a/reactivexcomponent/reactivexcomponent/configuration/api_configuration.py +++ b/reactivexcomponent/reactivexcomponent/configuration/api_configuration.py @@ -1,14 +1,15 @@ from lxml import etree +from typing import Dict, Any, cast NAMESPACE = {'xmlns': 'http://xcomponent.com/DeploymentConfig.xsd'} -def format_fsharp_field(value): +def format_fsharp_field(value: Any) -> Dict[str, Any]: return {"Case": "Some", "Fields": [value]} -def find_state_machine_by_name(component, state_machine_name): - state_machine = None +def find_state_machine_by_name(component: etree.ElementTree, state_machine_name: str) -> etree.ElementTree: + state_machine: etree.ElementTree = None for state_machines in component.findall('xmlns:stateMachines', NAMESPACE): for state_mach in state_machines.findall('xmlns:stateMachine', NAMESPACE): if state_mach.attrib['name'] == state_machine_name: @@ -18,8 +19,8 @@ def find_state_machine_by_name(component, state_machine_name): return state_machine -def find_state_machine_by_code(component, state_machine_code): - state_machine = None +def find_state_machine_by_code(component: etree.ElementTree, state_machine_code: int) -> etree.ElementTree: + state_machine: etree.ElementTree = None for state_machines in component.findall('xmlns:stateMachines', NAMESPACE): for state_mach in state_machines.findall('xmlns:stateMachine', NAMESPACE): if int(state_mach.attrib['id']) == state_machine_code: @@ -29,7 +30,7 @@ def find_state_machine_by_code(component, state_machine_code): return state_machine -def find_state_by_code(state_machine, state_code): +def find_state_by_code(state_machine: etree.ElementTree, state_code: int) -> etree.ElementTree: state = None for stat in state_machine.findall('xmlns:states', NAMESPACE)[0].\ findall('xmlns:State', NAMESPACE): @@ -42,12 +43,12 @@ def find_state_by_code(state_machine, state_code): class APIConfiguration: - def __init__(self, file): - tree = etree.parse(file) - data = etree.tostring(tree) - self.root = etree.fromstring(data) + def __init__(self, file: str) -> None: + tree: etree.ElementTree = etree.parse(file) + data: str = etree.tostring(tree) + self.root: etree.ElementTree = etree.fromstring(data) - def _find_component(self, component_name): + def _find_component(self, component_name: str) -> etree.ElementTree: for component in ((self.root).findall('xmlns:codesConverter', NAMESPACE))[0].\ findall('xmlns:components', NAMESPACE)[0].\ findall('xmlns:component', NAMESPACE): @@ -55,13 +56,13 @@ def _find_component(self, component_name): return component return None - def _find_component_by_name(self, component_name): - component = self._find_component(component_name) + def _find_component_by_name(self, component_name: str) -> etree.ElementTree: + component: etree.ElementTree = self._find_component(component_name) if component is None: raise Exception('Component %s not found' % component_name) return component - def _find_component_name(self, component_code): + def _find_component_name(self, component_code: int) -> etree.ElementTree: for component in ((self.root).findall('xmlns:codesConverter', NAMESPACE))[0].\ findall('xmlns:components', NAMESPACE)[0].\ findall('xmlns:component', NAMESPACE): @@ -69,38 +70,39 @@ def _find_component_name(self, component_code): return component return None - def _find_component_by_code(self, component_code): - component = self._find_component_name(component_code) + def _find_component_by_code(self, component_code: int) -> etree.ElementTree: + component: etree.ElementTree = self._find_component_name(component_code) if component is None: raise Exception('Component %s not found' % component_code) return component - def get_component_code(self, component_name): + def get_component_code(self, component_name: str) -> etree.ElementTree: component = self._find_component_by_name(component_name) return int(component.attrib['id']) - def get_state_machine_code(self, component_name, state_machine_name): + def get_state_machine_code(self, component_name: str, state_machine_name: str) -> int: component = self._find_component_by_name(component_name) - state_machine = find_state_machine_by_name(component, state_machine_name) + state_machine = find_state_machine_by_name( + component, state_machine_name) return int(state_machine.attrib['id']) - def contains_publisher(self, component_code, state_machine_code, message_type): - return self._get_publisher(component_code, state_machine_code, message_type) != None + def contains_publisher(self, component_code: int, state_machine_code: int, message_type: str) -> bool: + return self._get_publisher(component_code, state_machine_code, message_type) is not None - def contains_state_machine(self, component_name, state_machine_name): + def contains_state_machine(self, component_name: str, state_machine_name: str) -> etree.ElementTree: component = self._find_component(component_name) contain = False if component is not None: for state_mach in component.findall('xmlns:stateMachines', NAMESPACE)[0].\ - findall('xmlns:stateMachine', NAMESPACE): + findall('xmlns:stateMachine', NAMESPACE): if state_mach.attrib['name'] == state_machine_name: contain = True return contain - def contains_subscriber(self, component_code, state_machine_code, event_type): - return self._get_subscriber(component_code, state_machine_code, event_type) != None + def contains_subscriber(self, component_code: int, state_machine_code: int, event_type: str) -> bool: + return self._get_subscriber(component_code, state_machine_code, event_type) is not None - def _get_publisher(self, component_code, state_machine_code, message_type): + def _get_publisher(self, component_code: int, state_machine_code: int, message_type: str) -> etree.ElementTree: publisher = None for publish in ((self.root).findall('xmlns:clientAPICommunication', NAMESPACE))[0] \ .findall('xmlns:publish', NAMESPACE): @@ -110,18 +112,20 @@ def _get_publisher(self, component_code, state_machine_code, message_type): publisher = publish return publisher - def get_publisher_details(self, component_code, state_machine_code, message_type): - publisher = self._get_publisher(component_code, state_machine_code, message_type) + def get_publisher_details(self, component_code: int, state_machine_code: int, message_type: str) -> Dict[str, str]: + publisher = self._get_publisher( + component_code, state_machine_code, message_type) if publisher is None: message = 'publisher not found - component code : ' \ ' %i - statemachine code : %i - message type : %s' - raise Exception(message % (component_code, state_machine_code, message_type)) + raise Exception(message % + (component_code, state_machine_code, message_type)) return { 'eventCode': int(publisher.attrib['eventCode']), 'routingKey': publisher.findall('xmlns:topic', NAMESPACE)[0].text } - def _get_subscriber(self, component_code, state_machine_code, event_type): + def _get_subscriber(self, component_code: int, state_machine_code: int, event_type: str) -> etree.ElementTree: subscriber = None for subscrib in ((self.root).findall('xmlns:clientAPICommunication', NAMESPACE))[0].\ findall('xmlns:subscribe', NAMESPACE): @@ -131,15 +135,17 @@ def _get_subscriber(self, component_code, state_machine_code, event_type): subscriber = subscrib return subscriber - def get_subscriber_topic(self, component_code, state_machine_code, event_type): - subscriber = self._get_subscriber(component_code, state_machine_code, event_type) + def get_subscriber_topic(self, component_code: int, state_machine_code: int, event_type: str) -> str: + subscriber = self._get_subscriber( + component_code, state_machine_code, event_type) if subscriber is None: raise Exception('Subscriber not found - component code: %i - statemachine code: %i' % (component_code, state_machine_code)) - return subscriber.findall('xmlns:topic', NAMESPACE)[0].text + return cast(str, subscriber.findall('xmlns:topic', NAMESPACE)[0].text) - def get_state_name(self, component_code, state_machine_code, state_code): + def get_state_name(self, component_code: int, state_machine_code: int, state_code: int) -> str: component = self._find_component_by_code(component_code) - state_machine = find_state_machine_by_code(component, state_machine_code) + state_machine = find_state_machine_by_code( + component, state_machine_code) state = find_state_by_code(state_machine, state_code) - return state.attrib["name"] + return cast(str, state.attrib["name"]) diff --git a/reactivexcomponent/reactivexcomponent/configuration/serializer.py b/reactivexcomponent/reactivexcomponent/configuration/serializer.py index d9030de..de20383 100644 --- a/reactivexcomponent/reactivexcomponent/configuration/serializer.py +++ b/reactivexcomponent/reactivexcomponent/configuration/serializer.py @@ -1,13 +1,14 @@ import json +from typing import Any, Dict -def get_header_with_incoming_type(): +def get_header_with_incoming_type() -> Dict[str, Any]: return { "IncomingType": 0 } -def to_websocket_input_format(data): +def to_websocket_input_format(data: Dict[str, Any]) -> str: # pylint: disable=unsubscriptable-object return '{0} {1} {2}'.format(data['RoutingKey'], data['ComponentCode'], @@ -15,16 +16,16 @@ def to_websocket_input_format(data): # pylint: enable=unsubscriptable-object -def command_data_websocket_format(command): +def command_data_websocket_format(command: Dict[str, Any]) -> str: return '{0} {1}'.format(command["Command"], json.dumps(command["Data"])) -def get_json_data(data): - return json.loads(data[data.index("{"):data.rindex("}") + 1]) +def get_json_data(data: Dict[str, Any]) -> Any: + return str, json.loads(data[data.index("{"):data.rindex("}") + 1]) -def deserialize(data): +def deserialize(data: str) -> Dict[str, Any]: list_data = data.split() command = list_data[0] topic = list_data[1] @@ -36,7 +37,7 @@ def deserialize(data): } -def deserialize_without_topic(data): +def deserialize_without_topic(data: str) -> Dict[str, Any]: list_data = data.split() command = list_data[0] string_data = " ".join(list_data[1:]) diff --git a/reactivexcomponent/reactivexcomponent/xcomponent_api.py b/reactivexcomponent/reactivexcomponent/xcomponent_api.py index 153eefa..a3d2bf8 100644 --- a/reactivexcomponent/reactivexcomponent/xcomponent_api.py +++ b/reactivexcomponent/reactivexcomponent/xcomponent_api.py @@ -1,10 +1,11 @@ +from typing import Callable, Any from reactivexcomponent.communication.xc_connection import XcConnection class XcomponentAPI: - def __init__(self): - self.connection = None + def __init__(self) -> None: + pass - def create_session(self, xc_api, server_url, callback): + def create_session(self, xc_api: str, server_url: str, callback: Callable[[Any, Any], None]) -> None: self.connection = XcConnection() self.connection.create_connection(xc_api, server_url, callback) diff --git a/reactivexcomponent/tests/test_session.py b/reactivexcomponent/tests/test_session.py deleted file mode 100644 index 85ea126..0000000 --- a/reactivexcomponent/tests/test_session.py +++ /dev/null @@ -1,25 +0,0 @@ -from reactivexcomponent.xcomponent_api import XcomponentAPI -from reactivexcomponent.communication.publisher import Publisher -from reactivexcomponent.communication.subscriber import Subscriber - -def state_machine_update_listener(data): - print(data) - -def callback(error,session): - if error is not None: - print('erreur') - return - subscriber = session.create_subscriber() - subscriber.subscribe('Devinette', 'DevinetteStatus', state_machine_update_listener) - - subscriber2 = session.create_subscriber() - subscriber2.subscribe('Devinette', 'DevinetteStatus', state_machine_update_listener) - subscriber2.dispose_observable_subscribers() - - subscriber3 = session.create_subscriber() - subscriber3.subscribe('Devinette', 'Devinette', state_machine_update_listener) - -xcApiFile="data\\WebSocket_NewDevinetteApi_test.xcApi" -serverURL="wss://localhost:443" -xcApi=XcomponentAPI() -xcApi.create_session(xcApiFile,serverURL,callback) diff --git a/reactivexcomponent/tests/unit/mock_socket.py b/reactivexcomponent/tests/unit/mock_socket.py new file mode 100644 index 0000000..d910f29 --- /dev/null +++ b/reactivexcomponent/tests/unit/mock_socket.py @@ -0,0 +1,26 @@ + +# Mocking server + + +class MockWebSocket: + + def __init__(self, server_url): + self.server_url = server_url + self.server = None + self.on_open = None + self.on_message = None + self.on_close = None + self.on_error = None + + +class MockServer: + def __init__(self, server_url): + self.server_url = server_url + self.client = None + + def init(self): + if self.client.server_url == self.server_url: + self.client.on_open() + else: + self.client.on_error() +# create_mock_server diff --git a/reactivexcomponent/tests/unit/mock_subscriber_dependencies.py b/reactivexcomponent/tests/unit/mock_subscriber_dependencies.py new file mode 100644 index 0000000..1d8ed00 --- /dev/null +++ b/reactivexcomponent/tests/unit/mock_subscriber_dependencies.py @@ -0,0 +1,65 @@ +import uuid +import json +from unittest.mock import MagicMock +from reactivexcomponent.configuration.websocket_bridge_configuration import WebsocketTopicKind + + +OUTPUT_TOPIC = "output.1_0.microservice1.Devinette.DevinetteStatus" +SNAPSHOT_TOPIC = "snapshot.1_0.microservice1.Devinette" +STATE_NAME = "stateName" +COMPONENT_CODE = -725052640 +STATE_MACHINE_CODE = -2027871621 +AGENT_ID = 1 +STATE_MACHINE_ID = 2 +JSON_MESSAGE = {"key": "value"} + + +configuration = MagicMock(["get_subscriber_topic", "get_component_code", "get_state_machine_code", "get_snapshot_topic", "get_state_name", + "contains_subscriber"], key="configuration") + +configuration.get_snapshot_topic = MagicMock(return_value=SNAPSHOT_TOPIC) + +configuration.get_state_name = MagicMock(return_value=STATE_NAME) + +configuration.get_component_code = MagicMock(return_value=COMPONENT_CODE) + +configuration.get_state_machine_code = MagicMock(return_value=STATE_MACHINE_CODE) + + +def create_mock_websocket(): + websocket = MagicMock(["send", "close"], key="websocket") + return websocket + + +correct_data = { + "Header": {"IncomingType": 0}, + "JsonMessage": json.dumps({"Topic": {"Key": OUTPUT_TOPIC, "kind": WebsocketTopicKind.Public}}) +} + + +json_data = { + "Header": { + "StateMachineCode": {"Case": "Some", "Fields": [STATE_MACHINE_CODE]}, + "ComponentCode": {"Case": "Some", "Fields": [COMPONENT_CODE]}, + "StateMachineId": {"Case": "Some", "Fields": [STATE_MACHINE_ID]}, + "AgentId": {"Case": "Some", "Fields": [AGENT_ID]}, + "StateCode": {"Case": "Some", "Fields": [0]} + }, + "JsonMessage": json.dumps(JSON_MESSAGE) +} + +update_response = "update " + "topic " + json.dumps(json_data) + +correct_received_data = { + "stateMachineRef": { + "StateMachineCode": json_data["Header"]["StateMachineCode"]["Fields"][0], + "ComponentCode": json_data["Header"]["ComponentCode"]["Fields"][0], + "AgentId": AGENT_ID, + "StateName": STATE_NAME, + "send": lambda JSON_MESSAGE: [] + }, + "jsonMessage": JSON_MESSAGE +} + +correct_subscribe_request = "subscribe " + json.dumps(correct_data) +correct_unsubscribe_request = "unsubscribe " + json.dumps(correct_data) diff --git a/reactivexcomponent/tests/unit/test_xc_connection.py b/reactivexcomponent/tests/unit/test_xc_connection.py new file mode 100644 index 0000000..94b2b72 --- /dev/null +++ b/reactivexcomponent/tests/unit/test_xc_connection.py @@ -0,0 +1,12 @@ +import unittest +import mock_socket + + +class TestConnection(unittest.TestCase): + + def setUp(self): + self.url = "wss:" + self.client = mock_socket.MockWebSocket(self.url) + self.server = mock_socket.MockServer(self.url) + self.client.server = self.server + self.server.client = self.client \ No newline at end of file