this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

twitter-to-sqlite auth and followers commands

Simon Willison 2e4b7c13

+499
+110
.circleci/config.yml
··· 1 + version: 2.1 2 + workflows: 3 + build_and_deploy: 4 + jobs: 5 + - build: 6 + filters: 7 + tags: 8 + only: /.*/ 9 + - test-python-install: 10 + version: "3.6" 11 + requires: 12 + - build 13 + - test-python-install: 14 + version: "3.7" 15 + requires: 16 + - build 17 + - deploy: 18 + requires: 19 + - build 20 + filters: 21 + tags: 22 + only: /[0-9]+(\.[0-9]+)*/ 23 + branches: 24 + ignore: /.*/ 25 + jobs: 26 + build: 27 + docker: 28 + - image: circleci/python:3.6 29 + steps: 30 + - checkout 31 + - restore_cache: 32 + key: v1-dependency-cache-{{ checksum "setup.py" }} 33 + - run: 34 + name: install python dependencies 35 + command: | 36 + python3 -m venv venv 37 + . venv/bin/activate 38 + pip install -e . 39 + - save_cache: 40 + key: v1-dependency-cache-{{ checksum "setup.py" }} 41 + paths: 42 + - "venv" 43 + - run: 44 + name: run tests 45 + command: | 46 + . venv/bin/activate 47 + pip install -e . 48 + pip install pytest 49 + pytest 50 + test-python-install: 51 + parameters: 52 + version: 53 + type: string 54 + default: latest 55 + docker: 56 + - image: circleci/python:3.6 57 + steps: 58 + - checkout 59 + - restore_cache: 60 + key: v1-dependency-cache-{{ checksum "setup.py" }} 61 + - run: 62 + name: install python dependencies 63 + command: | 64 + python3 -m venv venv 65 + . venv/bin/activate 66 + pip install -e . 67 + - save_cache: 68 + key: v1-dependency-cache-{{ checksum "setup.py" }} 69 + paths: 70 + - "venv" 71 + - run: 72 + name: run tests 73 + command: | 74 + . venv/bin/activate 75 + pip install -e . 76 + pip install pytest 77 + pytest 78 + deploy: 79 + docker: 80 + - image: circleci/python:3.6 81 + steps: 82 + - checkout 83 + - restore_cache: 84 + key: v1-dependency-cache-{{ checksum "setup.py" }} 85 + - run: 86 + name: install python dependencies 87 + command: | 88 + python3 -m venv venv 89 + . venv/bin/activate 90 + pip install -e . 91 + - save_cache: 92 + key: v1-dependency-cache-{{ checksum "setup.py" }} 93 + paths: 94 + - "venv" 95 + - run: 96 + name: init .pypirc 97 + command: | 98 + echo -e "[pypi]" >> ~/.pypirc 99 + echo -e "username = simonw" >> ~/.pypirc 100 + echo -e "password = $PYPI_PASSWORD" >> ~/.pypirc 101 + - run: 102 + name: create packages 103 + command: | 104 + python setup.py bdist_wheel 105 + - run: 106 + name: upload to pypi 107 + command: | 108 + . venv/bin/activate 109 + pip install twine 110 + twine upload dist/*
+8
.gitignore
··· 1 + .venv 2 + __pycache__/ 3 + *.py[cod] 4 + *$py.class 5 + venv 6 + .eggs 7 + .pytest_cache 8 + *.egg-info
+201
LICENSE
··· 1 + Apache License 2 + Version 2.0, January 2004 3 + http://www.apache.org/licenses/ 4 + 5 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 + 7 + 1. Definitions. 8 + 9 + "License" shall mean the terms and conditions for use, reproduction, 10 + and distribution as defined by Sections 1 through 9 of this document. 11 + 12 + "Licensor" shall mean the copyright owner or entity authorized by 13 + the copyright owner that is granting the License. 14 + 15 + "Legal Entity" shall mean the union of the acting entity and all 16 + other entities that control, are controlled by, or are under common 17 + control with that entity. For the purposes of this definition, 18 + "control" means (i) the power, direct or indirect, to cause the 19 + direction or management of such entity, whether by contract or 20 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 + outstanding shares, or (iii) beneficial ownership of such entity. 22 + 23 + "You" (or "Your") shall mean an individual or Legal Entity 24 + exercising permissions granted by this License. 25 + 26 + "Source" form shall mean the preferred form for making modifications, 27 + including but not limited to software source code, documentation 28 + source, and configuration files. 29 + 30 + "Object" form shall mean any form resulting from mechanical 31 + transformation or translation of a Source form, including but 32 + not limited to compiled object code, generated documentation, 33 + and conversions to other media types. 34 + 35 + "Work" shall mean the work of authorship, whether in Source or 36 + Object form, made available under the License, as indicated by a 37 + copyright notice that is included in or attached to the work 38 + (an example is provided in the Appendix below). 39 + 40 + "Derivative Works" shall mean any work, whether in Source or Object 41 + form, that is based on (or derived from) the Work and for which the 42 + editorial revisions, annotations, elaborations, or other modifications 43 + represent, as a whole, an original work of authorship. For the purposes 44 + of this License, Derivative Works shall not include works that remain 45 + separable from, or merely link (or bind by name) to the interfaces of, 46 + the Work and Derivative Works thereof. 47 + 48 + "Contribution" shall mean any work of authorship, including 49 + the original version of the Work and any modifications or additions 50 + to that Work or Derivative Works thereof, that is intentionally 51 + submitted to Licensor for inclusion in the Work by the copyright owner 52 + or by an individual or Legal Entity authorized to submit on behalf of 53 + the copyright owner. For the purposes of this definition, "submitted" 54 + means any form of electronic, verbal, or written communication sent 55 + to the Licensor or its representatives, including but not limited to 56 + communication on electronic mailing lists, source code control systems, 57 + and issue tracking systems that are managed by, or on behalf of, the 58 + Licensor for the purpose of discussing and improving the Work, but 59 + excluding communication that is conspicuously marked or otherwise 60 + designated in writing by the copyright owner as "Not a Contribution." 61 + 62 + "Contributor" shall mean Licensor and any individual or Legal Entity 63 + on behalf of whom a Contribution has been received by Licensor and 64 + subsequently incorporated within the Work. 65 + 66 + 2. Grant of Copyright License. Subject to the terms and conditions of 67 + this License, each Contributor hereby grants to You a perpetual, 68 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 + copyright license to reproduce, prepare Derivative Works of, 70 + publicly display, publicly perform, sublicense, and distribute the 71 + Work and such Derivative Works in Source or Object form. 72 + 73 + 3. Grant of Patent License. Subject to the terms and conditions of 74 + this License, each Contributor hereby grants to You a perpetual, 75 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 + (except as stated in this section) patent license to make, have made, 77 + use, offer to sell, sell, import, and otherwise transfer the Work, 78 + where such license applies only to those patent claims licensable 79 + by such Contributor that are necessarily infringed by their 80 + Contribution(s) alone or by combination of their Contribution(s) 81 + with the Work to which such Contribution(s) was submitted. If You 82 + institute patent litigation against any entity (including a 83 + cross-claim or counterclaim in a lawsuit) alleging that the Work 84 + or a Contribution incorporated within the Work constitutes direct 85 + or contributory patent infringement, then any patent licenses 86 + granted to You under this License for that Work shall terminate 87 + as of the date such litigation is filed. 88 + 89 + 4. Redistribution. You may reproduce and distribute copies of the 90 + Work or Derivative Works thereof in any medium, with or without 91 + modifications, and in Source or Object form, provided that You 92 + meet the following conditions: 93 + 94 + (a) You must give any other recipients of the Work or 95 + Derivative Works a copy of this License; and 96 + 97 + (b) You must cause any modified files to carry prominent notices 98 + stating that You changed the files; and 99 + 100 + (c) You must retain, in the Source form of any Derivative Works 101 + that You distribute, all copyright, patent, trademark, and 102 + attribution notices from the Source form of the Work, 103 + excluding those notices that do not pertain to any part of 104 + the Derivative Works; and 105 + 106 + (d) If the Work includes a "NOTICE" text file as part of its 107 + distribution, then any Derivative Works that You distribute must 108 + include a readable copy of the attribution notices contained 109 + within such NOTICE file, excluding those notices that do not 110 + pertain to any part of the Derivative Works, in at least one 111 + of the following places: within a NOTICE text file distributed 112 + as part of the Derivative Works; within the Source form or 113 + documentation, if provided along with the Derivative Works; or, 114 + within a display generated by the Derivative Works, if and 115 + wherever such third-party notices normally appear. The contents 116 + of the NOTICE file are for informational purposes only and 117 + do not modify the License. You may add Your own attribution 118 + notices within Derivative Works that You distribute, alongside 119 + or as an addendum to the NOTICE text from the Work, provided 120 + that such additional attribution notices cannot be construed 121 + as modifying the License. 122 + 123 + You may add Your own copyright statement to Your modifications and 124 + may provide additional or different license terms and conditions 125 + for use, reproduction, or distribution of Your modifications, or 126 + for any such Derivative Works as a whole, provided Your use, 127 + reproduction, and distribution of the Work otherwise complies with 128 + the conditions stated in this License. 129 + 130 + 5. Submission of Contributions. Unless You explicitly state otherwise, 131 + any Contribution intentionally submitted for inclusion in the Work 132 + by You to the Licensor shall be under the terms and conditions of 133 + this License, without any additional terms or conditions. 134 + Notwithstanding the above, nothing herein shall supersede or modify 135 + the terms of any separate license agreement you may have executed 136 + with Licensor regarding such Contributions. 137 + 138 + 6. Trademarks. This License does not grant permission to use the trade 139 + names, trademarks, service marks, or product names of the Licensor, 140 + except as required for reasonable and customary use in describing the 141 + origin of the Work and reproducing the content of the NOTICE file. 142 + 143 + 7. Disclaimer of Warranty. Unless required by applicable law or 144 + agreed to in writing, Licensor provides the Work (and each 145 + Contributor provides its Contributions) on an "AS IS" BASIS, 146 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 + implied, including, without limitation, any warranties or conditions 148 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 + PARTICULAR PURPOSE. You are solely responsible for determining the 150 + appropriateness of using or redistributing the Work and assume any 151 + risks associated with Your exercise of permissions under this License. 152 + 153 + 8. Limitation of Liability. In no event and under no legal theory, 154 + whether in tort (including negligence), contract, or otherwise, 155 + unless required by applicable law (such as deliberate and grossly 156 + negligent acts) or agreed to in writing, shall any Contributor be 157 + liable to You for damages, including any direct, indirect, special, 158 + incidental, or consequential damages of any character arising as a 159 + result of this License or out of the use or inability to use the 160 + Work (including but not limited to damages for loss of goodwill, 161 + work stoppage, computer failure or malfunction, or any and all 162 + other commercial damages or losses), even if such Contributor 163 + has been advised of the possibility of such damages. 164 + 165 + 9. Accepting Warranty or Additional Liability. While redistributing 166 + the Work or Derivative Works thereof, You may choose to offer, 167 + and charge a fee for, acceptance of support, warranty, indemnity, 168 + or other liability obligations and/or rights consistent with this 169 + License. However, in accepting such obligations, You may act only 170 + on Your own behalf and on Your sole responsibility, not on behalf 171 + of any other Contributor, and only if You agree to indemnify, 172 + defend, and hold each Contributor harmless for any liability 173 + incurred by, or claims asserted against, such Contributor by reason 174 + of your accepting any such warranty or additional liability. 175 + 176 + END OF TERMS AND CONDITIONS 177 + 178 + APPENDIX: How to apply the Apache License to your work. 179 + 180 + To apply the Apache License to your work, attach the following 181 + boilerplate notice, with the fields enclosed by brackets "[]" 182 + replaced with your own identifying information. (Don't include 183 + the brackets!) The text should be enclosed in the appropriate 184 + comment syntax for the file format. We also recommend that a 185 + file or class name and description of purpose be included on the 186 + same "printed page" as the copyright notice for easier 187 + identification within third-party archives. 188 + 189 + Copyright [yyyy] [name of copyright owner] 190 + 191 + Licensed under the Apache License, Version 2.0 (the "License"); 192 + you may not use this file except in compliance with the License. 193 + You may obtain a copy of the License at 194 + 195 + http://www.apache.org/licenses/LICENSE-2.0 196 + 197 + Unless required by applicable law or agreed to in writing, software 198 + distributed under the License is distributed on an "AS IS" BASIS, 199 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 + See the License for the specific language governing permissions and 201 + limitations under the License.
+11
README.md
··· 1 + # twitter-to-sqlite 2 + 3 + [![PyPI](https://img.shields.io/pypi/v/twitter-to-sqlite.svg)](https://pypi.org/project/twitter-to-sqlite/) 4 + [![CircleCI](https://circleci.com/gh/dogsheep/twitter-to-sqlite.svg?style=svg)](https://circleci.com/gh/dogsheep/twitter-to-sqlite) 5 + [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/dogsheep/twitter-to-sqlite/blob/master/LICENSE) 6 + 7 + Save data from Twitter to a SQLite database. 8 + 9 + ## How to install 10 + 11 + $ pip install twitter-to-sqlite
+32
setup.py
··· 1 + from setuptools import setup 2 + import os 3 + 4 + VERSION = "0.3" 5 + 6 + 7 + def get_long_description(): 8 + with open( 9 + os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md"), 10 + encoding="utf8", 11 + ) as fp: 12 + return fp.read() 13 + 14 + 15 + setup( 16 + name="twitter-to-sqlite", 17 + description="Save data from Twitter to a SQLite database", 18 + long_description=get_long_description(), 19 + long_description_content_type="text/markdown", 20 + author="Simon Willison", 21 + url="https://github.com/dogsheep/twitter-to-sqlite", 22 + license="Apache License, Version 2.0", 23 + version=VERSION, 24 + packages=["twitter_to_sqlite"], 25 + entry_points=""" 26 + [console_scripts] 27 + twitter-to-sqlite=twitter_to_sqlite.cli:cli 28 + """, 29 + install_requires=["sqlite-utils~=1.11", "requests-oauthlib~=1.2.0"], 30 + extras_require={"test": ["pytest"]}, 31 + tests_require=["twitter-to-sqlite[test]"], 32 + )
twitter_to_sqlite/__init__.py

This is a binary file and will not be displayed.

+86
twitter_to_sqlite/cli.py
··· 1 + import click 2 + import os 3 + import sqlite_utils 4 + import json 5 + from twitter_to_sqlite import utils 6 + 7 + 8 + @click.group() 9 + @click.version_option() 10 + def cli(): 11 + "Save data from Twitter to a SQLite database" 12 + 13 + 14 + @cli.command() 15 + @click.argument( 16 + "auth_path", 17 + type=click.Path(file_okay=True, dir_okay=False, allow_dash=False), 18 + default="auth.json", 19 + ) 20 + def auth(auth_path): 21 + "Save authentication credentials to a JSON file" 22 + click.echo("Create an app here: https://developer.twitter.com/en/apps") 23 + click.echo("Then navigate to 'Keys and tokens' and paste in the following:") 24 + click.echo() 25 + api_key = click.prompt("API key") 26 + api_secret_key = click.prompt("API secret key") 27 + access_token = click.prompt("Access token") 28 + access_token_secret = click.prompt("Access token secret") 29 + open(auth_path, "w").write( 30 + json.dumps( 31 + { 32 + "api_key": api_key, 33 + "api_secret_key": api_secret_key, 34 + "access_token": access_token, 35 + "access_token_secret": access_token_secret, 36 + }, 37 + indent=4, 38 + ) 39 + + "\n" 40 + ) 41 + 42 + 43 + @cli.command() 44 + @click.argument( 45 + "db_path", 46 + type=click.Path(file_okay=True, dir_okay=False, allow_dash=False), 47 + required=True, 48 + ) 49 + @click.argument( 50 + "auth_path", 51 + type=click.Path(file_okay=True, dir_okay=False, allow_dash=True, exists=True), 52 + default="auth.json", 53 + ) 54 + @click.option("--user_id", help="Numeric user ID") 55 + @click.option("--screen_name", help="Screen name") 56 + @click.option("--silent", is_flag=True, help="Disable progress bar") 57 + def followers(db_path, auth_path, user_id, screen_name, silent): 58 + "Save followers for specified user (defaults to authenticated user)" 59 + auth = json.load(open(auth_path)) 60 + session = utils.session_for_auth(auth) 61 + db = sqlite_utils.Database(db_path) 62 + fetched = [] 63 + # Get the follower count, so we can have a progress bar 64 + count = 0 65 + 66 + def go(update): 67 + for followers_chunk in utils.fetch_follower_chunks( 68 + session, user_id, screen_name 69 + ): 70 + fetched.extend(followers_chunk) 71 + with db.conn: 72 + db["followers"].upsert_all(followers_chunk) 73 + update(len(followers_chunk)) 74 + 75 + if not silent: 76 + profile = utils.get_profile(session, user_id, screen_name) 77 + screen_name = profile["screen_name"] 78 + count = profile["followers_count"] 79 + with click.progressbar( 80 + length=count, 81 + label="Importing {:,} followers for @{}".format(count, screen_name), 82 + ) as bar: 83 + go(bar.update) 84 + else: 85 + go(lambda x: None) 86 + open("/tmp/all.json", "w").write(json.dumps(fetched, indent=4))
+51
twitter_to_sqlite/utils.py
··· 1 + from requests_oauthlib import OAuth1Session 2 + import time 3 + import urllib.parse 4 + 5 + 6 + def session_for_auth(auth): 7 + return OAuth1Session( 8 + client_key=auth["api_key"], 9 + client_secret=auth["api_secret_key"], 10 + resource_owner_key=auth["access_token"], 11 + resource_owner_secret=auth["access_token_secret"], 12 + ) 13 + 14 + 15 + def fetch_follower_chunks(session, user_id, screen_name): 16 + cursor = -1 17 + users = [] 18 + while cursor: 19 + headers, body = fetch_followers(session, cursor, user_id, screen_name) 20 + yield body["users"] 21 + cursor = body["next_cursor"] 22 + time.sleep(61) # Rate limit = 15 per 15 minutes! 23 + 24 + 25 + def fetch_followers(session, cursor, user_id, screen_name): 26 + args = {"count": 200, "cursor": cursor} 27 + if user_id: 28 + args["user_id"] = user_id 29 + if screen_name: 30 + args["screen_name"] = screen_name 31 + r = session.get( 32 + "https://api.twitter.com/1.1/followers/list.json?" 33 + + urllib.parse.urlencode(args) 34 + ) 35 + return r.headers, r.json() 36 + 37 + 38 + def get_profile(session, user_id, screen_name): 39 + if not (user_id or screen_name): 40 + return session.get( 41 + "https://api.twitter.com/1.1/account/verify_credentials.json" 42 + ).json() 43 + args = {} 44 + if user_id: 45 + args["user_id"] = user_id 46 + if screen_name: 47 + args["screen_name"] = screen_name 48 + url = "https://api.twitter.com/1.1/users/show.json" 49 + if args: 50 + url += "?" + urllib.parse.urlencode(args) 51 + return session.get(url).json()