[doc] Ajout de la création d'un modules
[bot_base] Nouvelle gestion des modules [config] Mise à jour de la doc
This commit is contained in:
parent
f6b8448cf8
commit
240d7dd417
133
Pipfile.lock
generated
133
Pipfile.lock
generated
@ -31,6 +31,7 @@
|
|||||||
"sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59",
|
"sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59",
|
||||||
"sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"
|
"sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"
|
||||||
],
|
],
|
||||||
|
"markers": "python_full_version >= '3.5.3'",
|
||||||
"version": "==3.6.2"
|
"version": "==3.6.2"
|
||||||
},
|
},
|
||||||
"async-timeout": {
|
"async-timeout": {
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||||
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||||
],
|
],
|
||||||
|
"markers": "python_full_version >= '3.5.3'",
|
||||||
"version": "==3.0.1"
|
"version": "==3.0.1"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
@ -45,6 +47,7 @@
|
|||||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==19.3.0"
|
"version": "==19.3.0"
|
||||||
},
|
},
|
||||||
"cffi": {
|
"cffi": {
|
||||||
@ -103,43 +106,46 @@
|
|||||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.9"
|
"version": "==2.9"
|
||||||
},
|
},
|
||||||
"multidict": {
|
"multidict": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1",
|
"sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a",
|
||||||
"sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35",
|
"sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000",
|
||||||
"sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928",
|
"sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2",
|
||||||
"sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969",
|
"sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507",
|
||||||
"sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e",
|
"sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5",
|
||||||
"sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78",
|
"sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7",
|
||||||
"sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1",
|
"sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d",
|
||||||
"sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136",
|
"sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463",
|
||||||
"sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8",
|
"sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19",
|
||||||
"sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2",
|
"sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3",
|
||||||
"sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e",
|
"sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b",
|
||||||
"sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4",
|
"sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c",
|
||||||
"sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5",
|
"sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87",
|
||||||
"sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd",
|
"sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7",
|
||||||
"sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab",
|
"sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430",
|
||||||
"sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20",
|
"sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255",
|
||||||
"sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"
|
"sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"
|
||||||
],
|
],
|
||||||
"version": "==4.7.5"
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==4.7.6"
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
|
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||||
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
|
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==20.3"
|
"version": "==20.4"
|
||||||
},
|
},
|
||||||
"pycparser": {
|
"pycparser": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.20"
|
"version": "==2.20"
|
||||||
},
|
},
|
||||||
"pynacl": {
|
"pynacl": {
|
||||||
@ -173,22 +179,24 @@
|
|||||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.4.7"
|
"version": "==2.4.7"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||||
],
|
],
|
||||||
"version": "==1.14.0"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.15.0"
|
||||||
},
|
},
|
||||||
"toml": {
|
"toml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
|
||||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.10.0"
|
"version": "==0.10.1"
|
||||||
},
|
},
|
||||||
"websockets": {
|
"websockets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -215,6 +223,7 @@
|
|||||||
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
|
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
|
||||||
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
|
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
|
||||||
],
|
],
|
||||||
|
"markers": "python_full_version >= '3.6.1'",
|
||||||
"version": "==8.1"
|
"version": "==8.1"
|
||||||
},
|
},
|
||||||
"yarl": {
|
"yarl": {
|
||||||
@ -237,6 +246,7 @@
|
|||||||
"sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080",
|
"sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080",
|
||||||
"sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"
|
"sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.4.2"
|
"version": "==1.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -253,6 +263,7 @@
|
|||||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==19.3.0"
|
"version": "==19.3.0"
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
@ -260,14 +271,15 @@
|
|||||||
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
|
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
|
||||||
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
|
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.8.0"
|
"version": "==2.8.0"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
|
"sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1",
|
||||||
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
|
"sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"
|
||||||
],
|
],
|
||||||
"version": "==2020.4.5.1"
|
"version": "==2020.4.5.2"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -281,6 +293,7 @@
|
|||||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==0.16"
|
"version": "==0.16"
|
||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
@ -288,6 +301,7 @@
|
|||||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.9"
|
"version": "==2.9"
|
||||||
},
|
},
|
||||||
"imagesize": {
|
"imagesize": {
|
||||||
@ -295,6 +309,7 @@
|
|||||||
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
|
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
|
||||||
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
|
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.2.0"
|
"version": "==1.2.0"
|
||||||
},
|
},
|
||||||
"jinja2": {
|
"jinja2": {
|
||||||
@ -302,6 +317,7 @@
|
|||||||
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
|
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
|
||||||
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
|
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.11.2"
|
"version": "==2.11.2"
|
||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
@ -340,28 +356,31 @@
|
|||||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.1.1"
|
"version": "==1.1.1"
|
||||||
},
|
},
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c",
|
"sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be",
|
||||||
"sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"
|
"sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"
|
||||||
],
|
],
|
||||||
"version": "==8.2.0"
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==8.3.0"
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
|
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||||
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
|
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==20.3"
|
"version": "==20.4"
|
||||||
},
|
},
|
||||||
"pluggy": {
|
"pluggy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==0.13.1"
|
"version": "==0.13.1"
|
||||||
},
|
},
|
||||||
"py": {
|
"py": {
|
||||||
@ -369,6 +388,7 @@
|
|||||||
"sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
|
"sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
|
||||||
"sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
|
"sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.8.1"
|
"version": "==1.8.1"
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
@ -376,6 +396,7 @@
|
|||||||
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
||||||
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
|
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==2.6.1"
|
"version": "==2.6.1"
|
||||||
},
|
},
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
@ -383,36 +404,39 @@
|
|||||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.4.7"
|
"version": "==2.4.7"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172",
|
"sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1",
|
||||||
"sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"
|
"sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.4.1"
|
"version": "==5.4.3"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
|
||||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
|
||||||
],
|
],
|
||||||
"version": "==2019.3"
|
"version": "==2020.1"
|
||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
||||||
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.23.0"
|
"version": "==2.23.0"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||||
],
|
],
|
||||||
"version": "==1.14.0"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.15.0"
|
||||||
},
|
},
|
||||||
"snowballstemmer": {
|
"snowballstemmer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -423,11 +447,11 @@
|
|||||||
},
|
},
|
||||||
"sphinx": {
|
"sphinx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3145d87d0962366d4c5264c39094eae3f5788d01d4b1a12294051bfe4271d91b",
|
"sha256:1c445320a3310baa5ccb8d957267ef4a0fc930dc1234db5098b3d7af14fbb242",
|
||||||
"sha256:d7c6e72c6aa229caf96af82f60a0d286a1521d42496c226fe37f5a75dcfe2941"
|
"sha256:7d3d5087e39ab5a031b75588e9859f011de70e213cd0080ccbc28079fb0786d1"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.0.2"
|
"version": "==3.1.0"
|
||||||
},
|
},
|
||||||
"sphinx-rtd-theme": {
|
"sphinx-rtd-theme": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -442,6 +466,7 @@
|
|||||||
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
|
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
|
||||||
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
|
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.0.2"
|
"version": "==1.0.2"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-devhelp": {
|
"sphinxcontrib-devhelp": {
|
||||||
@ -449,6 +474,7 @@
|
|||||||
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
|
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
|
||||||
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
|
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.0.2"
|
"version": "==1.0.2"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-htmlhelp": {
|
"sphinxcontrib-htmlhelp": {
|
||||||
@ -456,6 +482,7 @@
|
|||||||
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
|
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
|
||||||
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
|
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.0.3"
|
"version": "==1.0.3"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-jsmath": {
|
"sphinxcontrib-jsmath": {
|
||||||
@ -463,6 +490,7 @@
|
|||||||
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
||||||
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.0.1"
|
"version": "==1.0.1"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-qthelp": {
|
"sphinxcontrib-qthelp": {
|
||||||
@ -470,6 +498,7 @@
|
|||||||
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
|
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
|
||||||
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
|
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.0.3"
|
"version": "==1.0.3"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-serializinghtml": {
|
"sphinxcontrib-serializinghtml": {
|
||||||
@ -477,6 +506,7 @@
|
|||||||
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
|
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
|
||||||
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
|
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==1.1.4"
|
"version": "==1.1.4"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
@ -484,6 +514,7 @@
|
|||||||
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
|
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
|
||||||
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
|
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'",
|
||||||
"version": "==1.25.9"
|
"version": "==1.25.9"
|
||||||
},
|
},
|
||||||
"watchgod": {
|
"watchgod": {
|
||||||
@ -496,10 +527,10 @@
|
|||||||
},
|
},
|
||||||
"wcwidth": {
|
"wcwidth": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1",
|
"sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f",
|
||||||
"sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"
|
"sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"
|
||||||
],
|
],
|
||||||
"version": "==0.1.9"
|
"version": "==0.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ Introduction
|
|||||||
|
|
||||||
"Python Discord Bot" is a fully modular, self-hostable discord bot.
|
"Python Discord Bot" is a fully modular, self-hostable discord bot.
|
||||||
|
|
||||||
Its goal is to provide a solid and minimal base (only error handling, help, modules and configuration management) and to provide a large amount of modules.
|
Its goal is to provide a solid and minimal base (only error handling, modules and configuration management) and to provide a large amount of modules.
|
||||||
|
|
||||||
In addition to being fully modular, this bot is meant to be a single server, in order to allow advanced configuration and simple management of private messages (many modules are games that need to use private messages, and it wouldn't be nice to add a choice of server for each action).
|
In addition to being fully modular, this bot is meant to be a single server, in order to allow advanced configuration and simple management of private messages (many modules are games that need to use private messages, and it wouldn't be nice to add a choice of server for each action).
|
||||||
|
|
||||||
|
34
doc/source/module_creation/basic_module.rst
Normal file
34
doc/source/module_creation/basic_module.rst
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
Basic module
|
||||||
|
============
|
||||||
|
|
||||||
|
In last part we learn how to create a module from scratch, but as it is very long and difficult, we will use other
|
||||||
|
modules to create our first module. Theses modules are called metamodules, as they don't provide ``__main_class__`` or
|
||||||
|
``__dispatch__`` method, but only useful things for module developpers.
|
||||||
|
|
||||||
|
Let's use ``mod_base``!
|
||||||
|
|
||||||
|
First we need to install ``mod_base``, so follow instruction on PDMI. Then we need to add this module as dependency
|
||||||
|
to our module:
|
||||||
|
|
||||||
|
.. code-block:: toml
|
||||||
|
:linesnos:
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
bot_version = "~=0.2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bot_base = "~=1.2.0"
|
||||||
|
|
||||||
|
Now, mod_base is available, so we can use it in our module:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
import mod_base as m_b
|
||||||
|
|
||||||
|
class MyModule:
|
||||||
|
def __dispatch__(self, event_name, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
__main_class__ = MyModule
|
||||||
|
|
@ -6,3 +6,4 @@ Module creation
|
|||||||
:caption: Contents:
|
:caption: Contents:
|
||||||
|
|
||||||
intro
|
intro
|
||||||
|
basic_module
|
@ -1,67 +1,49 @@
|
|||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
Creating a module is relatively simple: just create a python package (a folder that contains a ``__init__.py`` file) in
|
A PDB module is a simple python package, ie. a folder which contains a ``__init__.py`` file. A valid module must have a
|
||||||
the modules folder, insert a ``version.json`` file (which will allow you to add dependencies and general information for
|
file named ``infos.toml`` which contains some informations about the module, and ``__init__.py__`` must have one thing:
|
||||||
your module) and have a MainClass class in the ``__init__.py`` file.
|
a variable named ``__main_class__``, which point to a class, and this class must have a method named ``__dispatch__``.
|
||||||
|
|
||||||
So the next step is to create the :py:class:`MainClass`, which inherits from :py:class:`BaseClassPython`, here is a minimal example:
|
Lets look a simple example:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
└─── modules
|
||||||
|
└─── my_module
|
||||||
|
├── infos.toml
|
||||||
|
└── __init__.py
|
||||||
|
|
||||||
|
Now examine ``infos.toml`` file:
|
||||||
|
|
||||||
|
.. code-block:: toml
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
bot_version = "~=0.2.0"
|
||||||
|
|
||||||
|
This file is minimal, but necessary to describe your module, and fields are very clear:
|
||||||
|
|
||||||
|
- ``version`` is version of module
|
||||||
|
- ``bot_version`` is the required version of bot (the ``~=`` is to say version ``0.2.0`` or compatible)
|
||||||
|
|
||||||
|
You can refer to ``version`` section for more informations about ``version`` and ``bot_version`` fields, and
|
||||||
|
``infos file`` section for other fields of this file.
|
||||||
|
|
||||||
|
Now look at ``__init__.py``
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
class MainClass:
|
class MyModule:
|
||||||
name = "MyFirstModule"
|
def __dispatch__(self, event_name, *args, **kwargs):
|
||||||
help = {
|
pass
|
||||||
"description": "My first module",
|
|
||||||
"commands": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
As you can see it's very simple, from now on you can start the bot and load the module.
|
__main_class__ = MyModule
|
||||||
|
|
||||||
Currently it does nothing, so let's add a ``say`` command:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
:linenos:
|
|
||||||
:emphasize-lines: 6,10,11
|
|
||||||
|
|
||||||
class MainClass:
|
|
||||||
name = "MyFirstModule"
|
|
||||||
help = {
|
|
||||||
"description": "My first module",
|
|
||||||
"commands": {
|
|
||||||
"{prefix}{command} say <message>": "Bot send message <message>",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def com_say(self, message, args, kwargs):
|
|
||||||
await message.channel.send(args[0])
|
|
||||||
|
|
||||||
You can now reload the module and test the command ``!myfirstmodule say "Hello world"``.
|
|
||||||
|
|
||||||
You can see that without the quotation marks the returned message contains only the first word. Indeed each message is
|
|
||||||
processed to extract the module (here ``module``), the command (here ``say``) and the arguments. This is how the
|
|
||||||
arguments are processed:
|
|
||||||
|
|
||||||
|
|
||||||
``!mymodule say "Hello world" "Goodbye world"`` - ``args = ["Hello world", "Goodbye world"] kwargs=[]``
|
As you can see a module is very simple.
|
||||||
|
|
||||||
``!mymodule say --long-option -an -s "s value"`` - ``args = [] kwargs = [("long-option", None), ("a", None), ("n", None), ("s", "s value")]``
|
|
||||||
|
|
||||||
``!mymodule say -s "s value" "value"`` - ``args = ["value"] kwargs = [("s", "s value")]``
|
|
||||||
|
|
||||||
So let's add an ``-m`` option that adds the mention of the author to the message:
|
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
:linenos:
|
|
||||||
:lineno-start: 10
|
|
||||||
:emphasize-lines: 2,3,4
|
|
||||||
|
|
||||||
async def com_say(self, message, args, kwargs):
|
|
||||||
if 'm' in [k for k, v in kwargs]:
|
|
||||||
await message.channel.send(message.author.mention + args[0])
|
|
||||||
return
|
|
||||||
await message.channel.send(args[0])
|
|
||||||
|
|
||||||
|
``__dispatch__`` method will be called for each event, these events are listed in section ``events``. As you can see,
|
||||||
|
there is a lot of event types, and handle them manually will be very long, so there is a module, who parse them, and
|
||||||
|
call ``on_{event}`` method, and an other one who parse message to handle commands. In next part we learn to use them.
|
@ -11,12 +11,12 @@ import discord
|
|||||||
import toml
|
import toml
|
||||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||||
|
|
||||||
|
from bot_base.modules import ModuleManager
|
||||||
from config import Config, config_types
|
from config import Config, config_types
|
||||||
from config.config_types import factory
|
from config.config_types import factory
|
||||||
import errors
|
import errors
|
||||||
|
|
||||||
__version__ = "0.2.0"
|
__version__ = "0.2.0"
|
||||||
MINIMAL_INFOS = ["version", "bot_version"]
|
|
||||||
|
|
||||||
|
|
||||||
class BotBase(discord.Client):
|
class BotBase(discord.Client):
|
||||||
@ -29,179 +29,32 @@ class BotBase(discord.Client):
|
|||||||
os.makedirs(data_folder, exist_ok=True)
|
os.makedirs(data_folder, exist_ok=True)
|
||||||
# Add module folder to search path
|
# Add module folder to search path
|
||||||
# TODO: Vérifier que ca ne casse rien
|
# TODO: Vérifier que ca ne casse rien
|
||||||
sys.path.insert(0, modules_folder)
|
|
||||||
# Setup logging
|
# Setup logging
|
||||||
self.log = logging.getLogger('bot_base')
|
self.log = logging.getLogger('bot_base')
|
||||||
# Content: {"module_name": {"module": imported module, "class": initialized class}}
|
|
||||||
self.modules = {}
|
|
||||||
|
|
||||||
# Setup config
|
# Setup config
|
||||||
self.configs = {}
|
self.configs = {}
|
||||||
|
|
||||||
self.config = Config(path=os.path.join(data_folder, "config.toml"))
|
self.config = Config(path=os.path.join(data_folder, "config.toml"))
|
||||||
self.config.register("modules", factory(config_types.List, factory(config_types.Str)))
|
|
||||||
self.config.register("data_folder", factory(config_types.Str))
|
self.config.register("data_folder", factory(config_types.Str))
|
||||||
self.config.register("modules_folder", factory(config_types.Str))
|
|
||||||
|
|
||||||
self.config.set({
|
self.config.set({
|
||||||
"modules": [],
|
|
||||||
"data_folder": data_folder,
|
"data_folder": data_folder,
|
||||||
"modules_folder": modules_folder,
|
|
||||||
}, no_save=True)
|
}, no_save=True)
|
||||||
|
|
||||||
self.config.load()
|
self.config.load()
|
||||||
|
|
||||||
|
self.modules = ModuleManager(self)
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
self.info("Bot ready.")
|
self.info("Bot ready.")
|
||||||
try:
|
self.modules.load_modules()
|
||||||
self.load_modules()
|
|
||||||
except errors.ModuleException as e:
|
|
||||||
self.loop.stop()
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def load_modules(self):
|
|
||||||
self.info("Load modules...")
|
|
||||||
for module in self.config["modules"]:
|
|
||||||
if module not in self.modules.keys():
|
|
||||||
self.load_module(module)
|
|
||||||
self.info("Modules loaded.")
|
|
||||||
|
|
||||||
def load_module(self, module: str) -> None:
|
|
||||||
"""
|
|
||||||
Try to load module
|
|
||||||
|
|
||||||
:raise ModuleNotFoundError: If module is not in module folder
|
|
||||||
:raise IncompatibleModuleError: If module is incompatible
|
|
||||||
:param str module: module to load
|
|
||||||
"""
|
|
||||||
self.info(f"Attempt to load module {module}...")
|
|
||||||
# Check if module exists
|
|
||||||
if not os.path.isdir(os.path.join(self.config["modules_folder"], module)):
|
|
||||||
self.warning(f"Attempt to load unknown module {module}.")
|
|
||||||
raise errors.ModuleNotFoundError(
|
|
||||||
f"Module {module} not found in modules folder ({self.config['modules_folder']}.)")
|
|
||||||
if not os.path.isfile(os.path.join(self.config["modules_folder"], module, "infos.toml")):
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: no infos.toml found")
|
|
||||||
raise errors.IncompatibleModuleError(f"Module {module} is incompatible: no infos.toml found.")
|
|
||||||
# Check infos.toml integrity
|
|
||||||
with open(os.path.join(self.config["modules_folder"], module, "infos.toml")) as f:
|
|
||||||
infos = toml.load(f)
|
|
||||||
for key in MINIMAL_INFOS:
|
|
||||||
if key not in infos.keys():
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: missing information {key}")
|
|
||||||
raise errors.IncompatibleModuleError(f"Missing information for module {module}: missing {key}.")
|
|
||||||
# Check bot_version
|
|
||||||
bot_version_specifier = SpecifierSet(infos["bot_version"])
|
|
||||||
if __version__ not in bot_version_specifier:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: need bot version {infos['bot_version']} "
|
|
||||||
f"and you have {__version__}")
|
|
||||||
raise errors.IncompatibleModuleError(f"Module {module} is not compatible with your current bot version "
|
|
||||||
f"(need {infos['bot_version']} and you have {__version__}).")
|
|
||||||
# Check dependencies
|
|
||||||
if infos.get("dependencies"):
|
|
||||||
for dep, version in infos["dependencies"].items():
|
|
||||||
if not dep in self.modules.keys():
|
|
||||||
self.load_module(dep)
|
|
||||||
dep_version_specifier = None
|
|
||||||
try:
|
|
||||||
dep_version_specifier = SpecifierSet(version)
|
|
||||||
except InvalidSpecifier:
|
|
||||||
self.warning(
|
|
||||||
f"Attempt to load incompatible module {module}: dependance version is invalid ({version} for {dep})")
|
|
||||||
raise errors.IncompatibleModuleError(f"Module {module} is not compatible with your current "
|
|
||||||
f"installation (version specifier {version} for {dep} is "
|
|
||||||
f"invalid.")
|
|
||||||
if self.modules[dep]["infos"]["version"] not in dep_version_specifier:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: require {dep} ({version}) "
|
|
||||||
f"and you have {dep} ({self.modules[dep]['infos']['version']})")
|
|
||||||
raise errors.IncompatibleModuleError(f"Module {module} is not compatible with your current install "
|
|
||||||
f"(require {dep} ({version}) and you have {dep} "
|
|
||||||
f"({self.modules[dep]['infos']['version']})")
|
|
||||||
|
|
||||||
# Check if module is meta
|
|
||||||
if infos.get("metamodule", False) == False:
|
|
||||||
# Check if module have __main_class__
|
|
||||||
try:
|
|
||||||
imported = importlib.import_module(module)
|
|
||||||
except Exception as e:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: failed import")
|
|
||||||
raise e
|
|
||||||
try:
|
|
||||||
main_class = imported.__main_class__
|
|
||||||
except AttributeError:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: no __main_class__ found")
|
|
||||||
raise errors.IncompatibleModuleError(f"Module {module} does not provide __main_class__.")
|
|
||||||
# Check if __main_class__ is a class
|
|
||||||
if not inspect.isclass(main_class):
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: __main_class__ is not a type")
|
|
||||||
raise errors.IncompatibleModuleError(f"Module {module} contains __main_class__ but it is not a type.")
|
|
||||||
try:
|
|
||||||
main_class = main_class(self)
|
|
||||||
except TypeError:
|
|
||||||
# Module don't need client reference
|
|
||||||
main_class = main_class()
|
|
||||||
# Check if __main_class__ have __dispatch__ attribute
|
|
||||||
try:
|
|
||||||
dispatch = main_class.__dispatch__
|
|
||||||
except AttributeError:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch_ not found")
|
|
||||||
raise errors.IncompatibleModuleError(
|
|
||||||
f"Module {module} mainclass ({main_class}) does not provide __dispatch__"
|
|
||||||
f" attribute)")
|
|
||||||
# Check if __dispatch__ is function
|
|
||||||
if not inspect.isfunction(imported.__main_class__.__dispatch__):
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch__ is not a function")
|
|
||||||
raise errors.IncompatibleModuleError(
|
|
||||||
f"Module {module} mainclass ({main_class}) provides __dispatch__, but it is "
|
|
||||||
f"not a function ({dispatch}).")
|
|
||||||
# Check if __dispatch__ can have variable positional and keyword aguments (to avoid future error on each event)
|
|
||||||
sig = inspect.signature(dispatch)
|
|
||||||
args_present, kwargs_present = False, False
|
|
||||||
for p in sig.parameters.values():
|
|
||||||
if p.kind == p.VAR_POSITIONAL:
|
|
||||||
args_present = True
|
|
||||||
elif p.kind == p.VAR_KEYWORD:
|
|
||||||
kwargs_present = True
|
|
||||||
if not args_present:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch__ doesn't accept variable "
|
|
||||||
f"positional arguments")
|
|
||||||
raise errors.IncompatibleModuleError(
|
|
||||||
f"Module {module} mainclass ({main_class}) provide __dispatch__ function, but "
|
|
||||||
f"this function doesn't accept variable positional arguments.")
|
|
||||||
if not kwargs_present:
|
|
||||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch__ doesn't accept variable "
|
|
||||||
f"keywords arguments.")
|
|
||||||
raise errors.IncompatibleModuleError(
|
|
||||||
f"Module {module} mainclass ({main_class}) provide __dispatch__ function, but "
|
|
||||||
f"this function doesn't accept variable keywords arguments.")
|
|
||||||
# Module is compatible!
|
|
||||||
# Add module to loaded modules
|
|
||||||
self.info(f"Add modules {module} to current modules.")
|
|
||||||
self.modules.update({
|
|
||||||
module: {
|
|
||||||
"infos": infos,
|
|
||||||
"imported": imported,
|
|
||||||
"initialized_class": main_class,
|
|
||||||
"dispatch": dispatch,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else: # Module is metamodule
|
|
||||||
self.info(f"Add modules {module} to current modules")
|
|
||||||
self.modules.update({
|
|
||||||
module: {
|
|
||||||
"infos": infos,
|
|
||||||
"dispatch": lambda *x, **y: None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if module not in self.config["modules"]:
|
|
||||||
self.config.set({"modules": self.config["modules"] + [module]})
|
|
||||||
self.config.save()
|
|
||||||
|
|
||||||
def dispatch(self, event, *args, **kwargs):
|
def dispatch(self, event, *args, **kwargs):
|
||||||
"""Dispatch event"""
|
"""Dispatch event"""
|
||||||
super().dispatch(event, *args, **kwargs)
|
super().dispatch(event, *args, **kwargs)
|
||||||
for module in self.modules.values():
|
for module in self.modules:
|
||||||
module["dispatch"](event, *args, **kwargs)
|
module.dispatch(event, *args, **kwargs)
|
||||||
|
|
||||||
async def on_error(self, event_method, *args, **kwargs):
|
async def on_error(self, event_method, *args, **kwargs):
|
||||||
self.error(f"Error in {event_method}: \n{traceback.format_exc()}")
|
self.error(f"Error in {event_method}: \n{traceback.format_exc()}")
|
||||||
@ -225,6 +78,9 @@ class BotBase(discord.Client):
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
def get_config(self, path):
|
def get_config(self, path):
|
||||||
|
path = os.path.join(self.config["data_folder"], path)
|
||||||
|
config = self.configs.get(path) or Config(path=path)
|
||||||
self.configs.update({
|
self.configs.update({
|
||||||
path: self.configs.get(path) or Config(path=path)
|
path: config
|
||||||
})
|
})
|
||||||
|
return config
|
||||||
|
139
src/bot_base/modules.py
Normal file
139
src/bot_base/modules.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import toml
|
||||||
|
from packaging.specifiers import SpecifierSet
|
||||||
|
import errors
|
||||||
|
from config import config_types
|
||||||
|
from config.base import BaseType
|
||||||
|
from config.config_types import factory
|
||||||
|
import typing
|
||||||
|
|
||||||
|
MINIMAL_INFOS = ["version", "bot_version"]
|
||||||
|
|
||||||
|
__version__ = "0.2.0"
|
||||||
|
|
||||||
|
class Dependency:
|
||||||
|
def __init__(self, name, version):
|
||||||
|
self.name = name
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
def __init__(self, module_manager, name):
|
||||||
|
self.name = name
|
||||||
|
self.module_manager = module_manager
|
||||||
|
self.__infos = None
|
||||||
|
self.__path = os.path.join(self.module_manager.config["modules_folder"], name)
|
||||||
|
|
||||||
|
self.__module = None
|
||||||
|
self.__class = None
|
||||||
|
self.__dispatch = lambda *x, **y: None
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
return self.__dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
"""Get version of module"""
|
||||||
|
return self.infos.get("version")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self):
|
||||||
|
"""Check if module exists"""
|
||||||
|
return os.path.isdir(self.__path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_infos(self):
|
||||||
|
"""Check if module contains all necessary files"""
|
||||||
|
if not self.exists:
|
||||||
|
raise errors.ModuleNotFoundError(f"Module {self.name} not found here: {self.__path}.")
|
||||||
|
return os.path.isfile(os.path.join(self.__path, "infos.toml"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def infos(self):
|
||||||
|
if not self.has_infos:
|
||||||
|
raise errors.IncompatibleModuleError(f"Module {self.name} doesn't have infos.toml.")
|
||||||
|
if not self.__infos:
|
||||||
|
with open(os.path.join(self.__path, "infos.toml")) as f:
|
||||||
|
self.__infos = toml.load(f)
|
||||||
|
return self.__infos
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_all_infos(self):
|
||||||
|
"""Check if all required infos are in infos.toml"""
|
||||||
|
for key in MINIMAL_INFOS:
|
||||||
|
if key not in self.infos.keys():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_compatible_with_client(self):
|
||||||
|
"""Check if module is compatible with bot version"""
|
||||||
|
bot_version_specifier = SpecifierSet(self.infos["bot_version"])
|
||||||
|
return __version__ in bot_version_specifier
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_metamodule(self):
|
||||||
|
"""Check if module is metamodule"""
|
||||||
|
return self.infos.get("metamodule", False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def deps(self):
|
||||||
|
deps = []
|
||||||
|
for dep, version in self.infos.get("dependencies", dict()).items():
|
||||||
|
deps.append(Dependency(dep, SpecifierSet(version)))
|
||||||
|
return deps
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
self.__module = importlib.import_module(self.name)
|
||||||
|
if not self.is_metamodule:
|
||||||
|
try:
|
||||||
|
# Try creating instance with client
|
||||||
|
self.__class = self.__module.__main_class__(self.module_manager.client)
|
||||||
|
except TypeError:
|
||||||
|
self.__class = self.__module.__main_class__()
|
||||||
|
self.__dispatch = self.__class.__dispatch__
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleManager:
|
||||||
|
def __init__(self, client):
|
||||||
|
self.client = client
|
||||||
|
self.modules = dict()
|
||||||
|
self.dispatch_modules = dict()
|
||||||
|
|
||||||
|
self.config = self.client.get_config("modules.toml")
|
||||||
|
self.config.register("modules_folder", factory(config_types.Str))
|
||||||
|
self.config.register("enabled_modules", factory(config_types.List, factory(config_types.Str)))
|
||||||
|
|
||||||
|
self.config.set({
|
||||||
|
"modules_folder": os.environ.get("LOCAL_MODULES", "modules"),
|
||||||
|
"enabled_modules": []
|
||||||
|
}, no_save=True)
|
||||||
|
self.config.load()
|
||||||
|
sys.path.insert(0, self.config["modules_folder"])
|
||||||
|
|
||||||
|
def load_module(self, name, version=None):
|
||||||
|
if name in self.modules.keys():
|
||||||
|
return
|
||||||
|
new_module = Module(self, name)
|
||||||
|
if version is not None and new_module.version not in version:
|
||||||
|
raise errors.MissingDependency(f"Incompatible version for dependency {name}: {new_module.version}, require {version}.")
|
||||||
|
for dep in new_module.deps:
|
||||||
|
self.load_module(dep.name, version=dep.version)
|
||||||
|
new_module.load()
|
||||||
|
self.modules.update({name: new_module})
|
||||||
|
if not new_module.is_metamodule:
|
||||||
|
self.dispatch_modules.update({name: new_module})
|
||||||
|
return
|
||||||
|
if version is None and name not in self.modules["enabled_modules"]:
|
||||||
|
self.config.set({"enabled_modules": self.config["enabled_modules"] + [name]})
|
||||||
|
self.config.save()
|
||||||
|
|
||||||
|
def load_modules(self):
|
||||||
|
for module in self.config["enabled_modules"]:
|
||||||
|
self.load_module(module)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.dispatch_modules.values().__iter__()
|
@ -103,8 +103,6 @@ class Config:
|
|||||||
>>> new_config.load() #doctest: +SKIP
|
>>> new_config.load() #doctest: +SKIP
|
||||||
>>> new_config["my_parameter"] #doctest: +SKIP
|
>>> new_config["my_parameter"] #doctest: +SKIP
|
||||||
3
|
3
|
||||||
|
|
||||||
:return: None
|
|
||||||
"""
|
"""
|
||||||
if self.path is not None:
|
if self.path is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -23,7 +23,7 @@ class Meta(type):
|
|||||||
|
|
||||||
def factory(type: Type[BaseType], *args, **kwargs):
|
def factory(type: Type[BaseType], *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a new test ``type`` with parameters args and kwargs
|
Create a new ``type`` with parameters args and kwargs
|
||||||
|
|
||||||
:Basic usage:
|
:Basic usage:
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ class Str(BaseType):
|
|||||||
|
|
||||||
:raise ValueError: if attempt to set invalid value
|
:raise ValueError: if attempt to set invalid value
|
||||||
:param str value: Value to set
|
:param str value: Value to set
|
||||||
:return: None
|
|
||||||
"""
|
"""
|
||||||
if not self.check_value(value):
|
if not self.check_value(value):
|
||||||
raise ValueError("Attempt to set incompatible value.")
|
raise ValueError("Attempt to set incompatible value.")
|
||||||
|
@ -12,3 +12,7 @@ class ModuleNotFoundError(ModuleException):
|
|||||||
|
|
||||||
class IncompatibleModuleError(ModuleException):
|
class IncompatibleModuleError(ModuleException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MissingDependency(ModuleException):
|
||||||
|
pass
|
||||||
|
@ -24,8 +24,8 @@ def setup_logging(default_path='data/log_config.json', default_level=logging.INF
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
setup_logging()
|
setup_logging()
|
||||||
client = BotBase(max_messages=500000, data_folder="datas",
|
print(os.environ.get("LOCAL_MODULES", "modules"))
|
||||||
modules_folder=os.environ.get("LOCAL_MODULES", "modules"))
|
client = BotBase(max_messages=500000, data_folder="datas")
|
||||||
|
|
||||||
async def start_bot():
|
async def start_bot():
|
||||||
await client.start(os.environ.get("DISCORD_TOKEN"))
|
await client.start(os.environ.get("DISCORD_TOKEN"))
|
||||||
|
Loading…
Reference in New Issue
Block a user