From 491ac3c762ca03b1ffbd3f1a6f9df5496653dede Mon Sep 17 00:00:00 2001 From: Eduardo Cuducos Date: Fri, 13 Mar 2020 13:29:27 -0400 Subject: [PATCH] Adds environment variables generator --- .env.sample | 19 ---- .github/workflows/mypy.yml | 25 +++++ .gitignore | 1 + Pipfile | 5 +- Pipfile.lock | 225 +++++++++++++++++++++++++------------ README.md | 39 +++---- bot_followers/__main__.py | 57 ---------- env.py | 159 ++++++++++++++++++++++++++ 8 files changed, 360 insertions(+), 170 deletions(-) delete mode 100644 .env.sample create mode 100644 .github/workflows/mypy.yml delete mode 100644 bot_followers/__main__.py create mode 100644 env.py diff --git a/.env.sample b/.env.sample deleted file mode 100644 index 66d1f9c..0000000 --- a/.env.sample +++ /dev/null @@ -1,19 +0,0 @@ -TWITTER_CONSUMER_KEY= -TWITTER_CONSUMER_SECRET=x -TWITTER_ACCESS_TOKEN_KEY= -TWITTER_ACCESS_TOKEN_SECRET= - -BOTOMETER_MASHAPE_KEY= - -DEBUG=False -ALLOWED_HOSTS=127.0.0.1,localhost -SECRET_KEY="a very complex string" - -DATABASE_URL=postgres://borsalino:ehmelhorjair@db/bot_followers -POSTGRES_PASSWORD=ehmelhorjair -POSTGRES_USER=borsalino -POSTGRES_DB=bot_followers - -CELERY_BROKER_URL=amqp://guest:guest@broker// - -CACHE_BOTOMETER_RESULTS_FOR_DAYS=180 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 0000000..7920a06 --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,25 @@ +name: Mypy + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@v1 + + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -U black + + - name: Run Mypy static type checker + run: mypy env.py diff --git a/.gitignore b/.gitignore index b5bba29..5f79212 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc *.sqlite3 .env +.mypy_cache/ __pycache__/ data/ staticfiles/ diff --git a/Pipfile b/Pipfile index 8a2e831..0cf69dd 100644 --- a/Pipfile +++ b/Pipfile @@ -8,12 +8,12 @@ flower = "*" ipdb = "*" isort = "*" ipython = "*" +mypy = "*" [packages] arrow = "*" botometer = "*" celery = "*" -click = "*" dj-database-url = "*" django = "*" django-cors-headers = "*" @@ -27,7 +27,8 @@ psycopg2-binary = "*" python-decouple = "*" tqdm = "*" tweepy = "*" -whitenoise = {extras = ["brotli"],version = "*"} +typer = {extras = ["click-completion"], version = "*"} +whitenoise = {extras = ["brotli"], version = "*"} [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index f5dd425..ba00732 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "493d06d528000cf6c9be54a46893aa41789bfab8fea1bea8af0ab878593fed69" + "sha256": "84a8bc19e3f42886e3fb3e24c32e82575e43ad3950a13b951acbbc26808ea424" }, "pipfile-spec": 6, "requires": { @@ -25,25 +25,25 @@ }, "arrow": { "hashes": [ - "sha256:01a16d8a93eddf86a29237f32ae36b29c27f047e79312eb4df5d55fd5a2b3183", - "sha256:e1a318a4c0b787833ae46302c02488b6eeef413c6a13324b3261ad320f21ec1e" + "sha256:5390e464e2c5f76971b60ffa7ee29c598c7501a294bc9f5e6dadcb251a5d027b", + "sha256:70729bcc831da496ca3cb4b7e89472c8e2d27d398908155e0796179f6d2d41ee" ], "index": "pypi", - "version": "==0.15.4" + "version": "==0.15.5" }, "asgiref": { "hashes": [ - "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", - "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" + "sha256:3e4192eaec0758b99722f0b0666d5fbfaa713054d92e8de5b58ba84ec5ce696f", + "sha256:c8f49dd3b42edcc51d09dd2eea8a92b3cfc987ff7e6486be734b4d0cbfd5d315" ], - "version": "==3.2.3" + "version": "==3.2.5" }, "billiard": { "hashes": [ - "sha256:26fd494dc3251f8ce1f5559744f18aeed427fdaf29a75d7baae26752a5d3816f", - "sha256:f4e09366653aa3cb3ae8ed16423f9ba1665ff426f087bcdbbed86bf3664fe02c" + "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", + "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" ], - "version": "==3.6.2.0" + "version": "==3.6.3.0" }, "botometer": { "hashes": [ @@ -92,11 +92,11 @@ }, "celery": { "hashes": [ - "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", - "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" + "sha256:3c5fcd6bfcf9a6323cb742cfc121d1790d50cfeddf300ba723cfa0b356413f07", + "sha256:a650525303ee866fb0c62c82f68681fcc2183eebbfafae552c27d30125fe518b" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.4.1" }, "certifi": { "hashes": [ @@ -114,11 +114,10 @@ }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", + "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" ], - "index": "pypi", - "version": "==7.0" + "version": "==7.1.1" }, "dj-database-url": { "hashes": [ @@ -130,27 +129,27 @@ }, "django": { "hashes": [ - "sha256:2f1ba1db8648484dd5c238fb62504777b7ad090c81c5f1fd8d5eb5ec21b5f283", - "sha256:c91c91a7ad6ef67a874a4f76f58ba534f9208412692a840e1d125eb5c279cb0a" + "sha256:50b781f6cbeb98f673aa76ed8e572a019a45e52bdd4ad09001072dfd91ab07c8", + "sha256:89e451bfbb815280b137e33e454ddd56481fdaa6334054e6e031041ee1eda360" ], "index": "pypi", - "version": "==3.0.3" + "version": "==3.0.4" }, "django-cors-headers": { "hashes": [ - "sha256:84933651fbbde8f2bc084bef2f077b79db1ec1389432f21dd661eaae6b3d6a95", - "sha256:a8b2772582e8025412f4d4b54b617d8b707076ffd53a2b961bd24f10ec207a7c" + "sha256:a5960addecc04527ab26617e51b8ed42f0adab4594b24bb0f3c33e2bd3857c3f", + "sha256:a785b5f446f6635810776d9f5f5d23e6a2a2f728ea982648370afaf0dfdf2627" ], "index": "pypi", - "version": "==3.2.0" + "version": "==3.2.1" }, "django-extensions": { "hashes": [ - "sha256:a9db7c56a556d244184f589f2437b4228de86ee45e5ebb837fb20c6d54e95ea5", - "sha256:b58320d3fe3d6ae7d1d8e38959713fa92272f4921e662d689058d942a5b444f7" + "sha256:1a03c4e8bade575f8c2be6c76456f8a2be3f9b02ab9f47d3535afa9562dc0493", + "sha256:2699cc1d6fb4bd393c0b5832fea4bc685f2ace5800b3c9ff222b2080f161ac04" ], "index": "pypi", - "version": "==2.2.5" + "version": "==2.2.8" }, "django-test-without-migrations": { "hashes": [ @@ -168,11 +167,11 @@ }, "freezegun": { "hashes": [ - "sha256:2a4d9c8cd3c04a201e20c313caf8b6338f1cfa4cda43f46a94cc4a9fd13ea5e7", - "sha256:edfdf5bc6040969e6ed2e36eafe277963bdc8b7c01daeda96c5c8594576c9390" + "sha256:82c757a05b7c7ca3e176bfebd7d6779fd9139c7cb4ef969c38a28d74deef89b2", + "sha256:e2062f2c7f95cc276a834c22f1a17179467176b624cc6f936e8bc3be5535ad1b" ], "index": "pypi", - "version": "==0.3.12" + "version": "==0.3.15" }, "gunicorn": { "hashes": [ @@ -184,17 +183,17 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "kombu": { "hashes": [ - "sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac", - "sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1" + "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76", + "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2" ], - "version": "==4.6.7" + "version": "==4.6.8" }, "mixer": { "hashes": [ @@ -287,10 +286,10 @@ }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "requests-oauthlib": { "hashes": [ @@ -308,10 +307,10 @@ }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], - "version": "==0.3.0" + "version": "==0.3.1" }, "text-unidecode": { "hashes": [ @@ -322,11 +321,11 @@ }, "tqdm": { "hashes": [ - "sha256:895796ea8df435b6f502bf122f2b2034a3d48e6d8ff52175606ac1051b0e3e12", - "sha256:e405d16c98fcf30725d0c9d493ed07302a18846b5452de5253030ccd18996f87" + "sha256:0d8b5afb66e23d80433102e9bd8b5c8b65d34c2a2255b2de58d97bd2ea8170fd", + "sha256:f35fb121bafa030bd94e74fcfd44f3c2830039a2ddef7fc87ef1c2d205237b24" ], "index": "pypi", - "version": "==4.40.1" + "version": "==4.43.0" }, "tweepy": { "hashes": [ @@ -336,6 +335,17 @@ "index": "pypi", "version": "==3.8.0" }, + "typer": { + "extras": [ + "click-completion" + ], + "hashes": [ + "sha256:69b27d2851ec9b828e9c9ddfa866796a5d81d9048ffc5ce595ce40a151f7307d", + "sha256:f31a2458e5818424605103b5e69e59cd1b1db2f73a01eb9c3f6afc1c36b92684" + ], + "index": "pypi", + "version": "==0.0.10" + }, "urllib3": { "hashes": [ "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", @@ -355,11 +365,11 @@ "brotli" ], "hashes": [ - "sha256:22f79cf8f1f509639330f93886acaece8ec5ac5e9600c3b981d33c34e8a42dfd", - "sha256:6dfea214b7c12efd689007abf9afa87a426586e9dbc051873ad2c8e535e2a1ac" + "sha256:0f9137f74bd95fa54329ace88d8dc695fbe895369a632e35f7a136e003e41d73", + "sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700" ], "index": "pypi", - "version": "==4.1.4" + "version": "==5.0.1" } }, "develop": { @@ -370,6 +380,14 @@ ], "version": "==2.5.2" }, + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, "babel": { "hashes": [ "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", @@ -386,25 +404,25 @@ }, "billiard": { "hashes": [ - "sha256:26fd494dc3251f8ce1f5559744f18aeed427fdaf29a75d7baae26752a5d3816f", - "sha256:f4e09366653aa3cb3ae8ed16423f9ba1665ff426f087bcdbbed86bf3664fe02c" + "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", + "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" ], - "version": "==3.6.2.0" + "version": "==3.6.3.0" }, "celery": { "hashes": [ - "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", - "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" + "sha256:3c5fcd6bfcf9a6323cb742cfc121d1790d50cfeddf300ba723cfa0b356413f07", + "sha256:a650525303ee866fb0c62c82f68681fcc2183eebbfafae552c27d30125fe518b" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.4.1" }, "decorator": { "hashes": [ - "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", - "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" ], - "version": "==4.4.1" + "version": "==4.4.2" }, "flower": { "hashes": [ @@ -415,18 +433,18 @@ }, "ipdb": { "hashes": [ - "sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd" + "sha256:77fb1c2a6fccdfee0136078c9ed6fe547ab00db00bebff181f1e8c9e13418d49" ], "index": "pypi", - "version": "==0.12.3" + "version": "==0.13.2" }, "ipython": { "hashes": [ - "sha256:c66c7e27239855828a764b1e8fc72c24a6f4498a2637572094a78c5551fb9d51", - "sha256:f186b01b36609e0c5d0de27c7ef8e80c990c70478f8c880863004b3489a9030e" + "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a", + "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333" ], "index": "pypi", - "version": "==7.10.1" + "version": "==7.13.0" }, "ipython-genutils": { "hashes": [ @@ -452,17 +470,44 @@ }, "kombu": { "hashes": [ - "sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac", - "sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1" + "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76", + "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2" + ], + "version": "==4.6.8" + }, + "mypy": { + "hashes": [ + "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2", + "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1", + "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164", + "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761", + "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce", + "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27", + "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754", + "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae", + "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9", + "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600", + "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65", + "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8", + "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913", + "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3" + ], + "index": "pypi", + "version": "==0.770" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" ], - "version": "==4.6.7" + "version": "==0.4.3" }, "parso": { "hashes": [ - "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", - "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095" + "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157", + "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995" ], - "version": "==0.6.1" + "version": "==0.6.2" }, "pexpect": { "hashes": [ @@ -481,10 +526,10 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e", - "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a" + "sha256:859e1b205b6cf6a51fa57fa34202e45365cf58f8338f0ee9f4e84a4165b37d5b", + "sha256:ebe6b1b08c888b84c50d7f93dee21a09af39860144ff6130aadbd61ae8d29783" ], - "version": "==3.0.3" + "version": "==3.0.4" }, "ptyprocess": { "hashes": [ @@ -495,10 +540,10 @@ }, "pygments": { "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.5.2" + "version": "==2.6.1" }, "pytz": { "hashes": [ @@ -533,6 +578,40 @@ ], "version": "==4.3.3" }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "version": "==1.4.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", + "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", + "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" + ], + "version": "==3.7.4.1" + }, "vine": { "hashes": [ "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", diff --git a/README.md b/README.md index f5198d0..f967cb0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Bot Followers [![GitHub Actions: Tests](https://github.com/cuducos/bot-followers/workflows/Tests/badge.svg)]() [![GitHub Actions: Black](https://github.com/cuducos/bot-followers/workflows/Black/badge.svg)]() +# Bot Followers [![GitHub Actions: Tests](https://github.com/cuducos/bot-followers/workflows/Tests/badge.svg)]() [![GitHub Actions: Black](https://github.com/cuducos/bot-followers/workflows/Black/badge.svg)]() [![GitHub Actions: Mypy](https://github.com/cuducos/bot-followers/workflows/Mypy/badge.svg)]() A web app to check whether followers of a given Twitter account are bots using [Botometer](https://botometer.iuni.iu.edu/). This repository started as a _fork_ of [Twitter Clean-up](https://github.com/cuducos/twitter-cleanup). @@ -7,30 +7,30 @@ Clean-up](https://github.com/cuducos/twitter-cleanup). If you're looking for the CLI version, [it's tagged](https://github.com/cuducos/bot-followers/tree/cli). -## Requirements +## Installing -* [Twitter API keys](https://developer.twitter.com/apps) -* [Botometer API key](https://market.mashape.com/OSoMe/botometer) -* [Docker Compose](https://docs.docker.com/compose/) for the **development** mode instructions -* [Dokku](http://dokku.viewdocs.io/dokku/) for the **production** mode (deploy) instructions +1. Make sure you meet these requirements: + 1. [Twitter API keys](https://developer.twitter.com/apps) + 1. [Botometer API key](https://market.mashape.com/OSoMe/botometer) + 1. **For development**, [Docker Compose](https://docs.docker.com/compose/) + 1. **For production**, [Dokku](http://dokku.viewdocs.io/dokku/) +1. Run `python env.py` to create a `.env` file with the required environment variables. Feel free to explore the options available with the `--help` argument. -## Development setup +### Development environment -Copy `.env.sample` as `.env` and fill the environment values as appropriated. I tried to use meaningful variables names, but fell free to [ask](https://github.com/cuducos/bot-followers/issues) if anything is not clear. To start the services use the default `docker-compose up`. -## Deploy setup +### Deploy environment -Having a Dokku-ready server, install the following Dokku plug-ins: +Having a Dokku-ready server: -* [PostgreSQL](https://github.com/dokku/dokku-postgres-plugin) (or other database of your choice) -* [RabbitMQ](https://github.com/dokku/dokku-rabbitmq-plugin) (or other Celery broker of your choice) -* [Let's Encrypt](https://github.com/dokku/dokku-letsencrypt) - -Create an app for Bot Followers in Dokku, add it as a remote repository in your local Git repository, and activate the plug-ins. - -For each variable in `.env.sample`, create an equivalent environment variable for your Dokku app. +1. Install the following Dokku plug-ins: + 1. [PostgreSQL](https://github.com/dokku/dokku-postgres-plugin) (or other database of your choice) + 1. [RabbitMQ](https://github.com/dokku/dokku-rabbitmq-plugin) (or other Celery broker of your choice) + 1. [Let's Encrypt](https://github.com/dokku/dokku-letsencrypt) +1. Create an app for _Bot Followers_ in Dokku, add it as a remote repository in your local Git repository, and activate the plug-ins. +1. For each variable in `.env`, create an equivalent environment variable for your Dokku app. ## Usage @@ -79,9 +79,10 @@ $ python manage.py purgecelerytasks ## Contributing -Please, write and run tests, and format your code with [Black](https://github.com/ambv/black): +Please, write tests, run checks, and format your code with [Black](https://github.com/ambv/black): ```bash -$ black . $ python manage.py test +$ mypy env.py +$ black . ``` diff --git a/bot_followers/__main__.py b/bot_followers/__main__.py deleted file mode 100644 index d9e612e..0000000 --- a/bot_followers/__main__.py +++ /dev/null @@ -1,57 +0,0 @@ -from time import sleep - -import click -from tweepy.error import RateLimitError - -from bot_followers import BotFollowers - -import traceback -from os import environ -from sys import exit - -try: - environ["TWITTER_CONSUMER_KEY"] - environ["TWITTER_CONSUMER_SECRET"] - environ["TWITTER_ACCESS_TOKEN_KEY"] - environ["TWITTER_ACCESS_TOKEN_SECRET"] - environ["BOTOMETER_MASHAPE_KEY"] - -except KeyError: - print("Environment variable(s) not found") - print("Please set it using .env file or env VAR=VALUE") - exit() - - -@click.group() -@click.argument("account") -@click.pass_context -def cli(ctx, account): - """Bot Follower is a tiny app to check whether followers of a given Twitter - account are bots.""" - ctx.obj = {"app": BotFollowers(account)} - - -@cli.command() -@click.pass_context -def analyze(ctx): - """Run the app to collect & analyze data.""" - while True: - try: - ctx.obj.get("app")() - except RateLimitError: - click.echo("Due to the Twitter API rate limit, let's take a breakā€¦") - sleep(60 * 5) - pass - else: - break - - -@cli.command() -@click.pass_context -def report(ctx): - """Print a simple report of stored results.""" - ctx.obj.get("app").report() - - -if __name__ == "__main__": - cli() diff --git a/env.py b/env.py new file mode 100644 index 0000000..59d554f --- /dev/null +++ b/env.py @@ -0,0 +1,159 @@ +from dataclasses import dataclass +from pathlib import Path +from string import ascii_letters, digits +from typing import Iterable, Iterator, Optional +from random import randint + +from typer import confirm, echo, prompt, run +from django.utils.crypto import get_random_string # type: ignore + + +@dataclass +class Config: + human_name: str + name: str + default: Optional[str] = None + + def value(self, use_default: bool = False): + if self.default and use_default: + return self.default + + return prompt(self.human_name, default=self.default) + + +@dataclass +class Group: + title: str + description: str + configs: Iterable[Config] + auto_variable: Optional[Iterable[str]] = None + + def __str__(self): + contents = ("", self.title, f"({self.description})") + return "\n".join(contents) + + def contents(self, use_defaults: bool = False) -> Iterator[str]: + settings = {config.name: config.value(use_defaults) for config in self.configs} + + if self.auto_variable: + name, value, *keys = self.auto_variable + replacements = tuple(settings[key] for key in keys) + value = value.format(*replacements) + settings[name] = value + + for key, value in settings.items(): + yield f"{key}={value}" + + +@dataclass +class Generator: + path: Path + overwrite: bool + use_defaults: bool + chars_for_secret_key: str = f"{ascii_letters}{digits}!@#$%^&*(-_=+)" + + @property + def secret_key(self) -> str: + return get_random_string(randint(64, 128), self.chars_for_secret_key) + + def __post_init__(self) -> None: + twitter = Group( + "Twitter API", + "You can get yours at https://developer.twitter.com/en/apps", + ( + Config("API Key", "TWITTER_CONSUMER_KEY"), + Config("API secret key", "TWITTER_CONSUMER_SECRET"), + Config("Access token", "TWITTER_ACCESS_TOKEN_KEY"), + Config("Access token secret", "TWITTER_ACCESS_TOKEN_SECRET"), + ), + ) + botometer = Group( + "Botometer API", + "You can get yours at https://rapidapi.com/OSoMe/api/botometer", + (Config("Application Key", "BOTOMETER_MASHAPE_KEY"),), + ) + django = Group( + "Django settings", + "You can find details about them at https://docs.djangoproject.com/3.0/en/topics/settings/", + ( + Config("Debug mode", "DEBUG", "False"), + Config("Allowed hosts", "ALLOWED_HOSTS", "127.0.0.1,localhost"), + Config("Secret key", "SECRET_KEY", self.secret_key), + ), + ) + postgres = Group( + "PostgreSQL credentials", + "Use the default values for Docker Compose within a developing environment", + ( + Config("Host", "POSTGRES_HOST", "db"), + Config("Database name", "POSTGRES_DB", "bot_followers"), + Config("User name", "POSTGRES_USER", "borsalino"), + Config("Password", "POSTGRES_PASSWORD", "ehmelhorjair"), + ), + ( + "DATABASE_URL", + "postgres://{}:{}@{}/{}", + "POSTGRES_USER", + "POSTGRES_PASSWORD", + "POSTGRES_HOST", + "POSTGRES_DB", + ), + ) + celery = Group( + "Celery settings", + "Use the default values for Docker Compose within a developing environment", + (Config("Broker URL", "CELERY_BROKER_URL", "amqp://guest:guest@broker//"),), + ) + cache = Group( + "Cache", + "This helps us to avoid unnecessary requests to Botometer API", + ( + Config( + "Number of days to keep results in cache", + "CACHE_BOTOMETER_RESULTS_FOR_DAYS", + "180", + ), + ), + ) + self.settings: Iterable[Group] = ( + twitter, + botometer, + django, + postgres, + celery, + cache, + ) + + def can_write_to_path(self) -> bool: + if self.overwrite or not self.path.exists(): + return True + + echo(f"There is an existing {self.path.name} file.") + return confirm("Do you want to overwrite it?") + + def contents(self) -> Iterator[str]: + for group in self.settings: + if not self.use_defaults or not all(c.default for c in group.configs): + echo(group) + + yield f"# {group.title}" + yield from group.contents(self.use_defaults) + yield "" + + def __call__(self) -> None: + if not self.can_write_to_path(): + return + + contents = "\n".join(self.contents()) + self.path.write_text(contents.strip()) + echo(f"{self.path.name} created!") + + +def main(file_name: str = ".env", overwrite: bool = False, use_defaults: bool = False): + """Creates a file with the environment variables to run Bot Followers. The + default file name is .env, but you can change it using --file-name option.""" + Generator(Path(file_name), overwrite, use_defaults)() + + +if __name__ == "__main__": + run(main)