├── .gitignore ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.rst ├── conftest.py ├── docs ├── Makefile ├── conf.py ├── docs │ └── images ├── images │ ├── first-time.gif │ └── next-time.gif └── index.rst ├── ipython_secrets.py ├── ipython_secrets_test.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos,python,jupyternotebook 2 | 3 | ### JupyterNotebook ### 4 | .ipynb_checkpoints 5 | */.ipynb_checkpoints/* 6 | 7 | # Remove previous ipynb_checkpoints 8 | # git rm -r .ipynb_checkpoints/ 9 | # 10 | ### macOS ### 11 | *.DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | 15 | # Icon must end with two \r 16 | Icon 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear in the root of a volume 22 | .DocumentRevisions-V100 23 | .fseventsd 24 | .Spotlight-V100 25 | .TemporaryItems 26 | .Trashes 27 | .VolumeIcon.icns 28 | .com.apple.timemachine.donotpresent 29 | 30 | # Directories potentially created on remote AFP share 31 | .AppleDB 32 | .AppleDesktop 33 | Network Trash Folder 34 | Temporary Items 35 | .apdisk 36 | 37 | ### Python ### 38 | # Byte-compiled / optimized / DLL files 39 | __pycache__/ 40 | *.py[cod] 41 | *$py.class 42 | 43 | # C extensions 44 | *.so 45 | 46 | # Distribution / packaging 47 | .Python 48 | build/ 49 | develop-eggs/ 50 | dist/ 51 | downloads/ 52 | eggs/ 53 | .eggs/ 54 | lib/ 55 | lib64/ 56 | parts/ 57 | sdist/ 58 | var/ 59 | wheels/ 60 | *.egg-info/ 61 | .installed.cfg 62 | *.egg 63 | 64 | # PyInstaller 65 | # Usually these files are written by a python script from a template 66 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 67 | *.manifest 68 | *.spec 69 | 70 | # Installer logs 71 | pip-log.txt 72 | pip-delete-this-directory.txt 73 | 74 | # Unit test / coverage reports 75 | htmlcov/ 76 | .tox/ 77 | .coverage 78 | .coverage.* 79 | .cache 80 | nosetests.xml 81 | coverage.xml 82 | *.cover 83 | .hypothesis/ 84 | 85 | # Translations 86 | *.mo 87 | *.pot 88 | 89 | # Django stuff: 90 | *.log 91 | local_settings.py 92 | 93 | # Flask stuff: 94 | instance/ 95 | .webassets-cache 96 | 97 | # Scrapy stuff: 98 | .scrapy 99 | 100 | # Sphinx documentation 101 | docs/_build/ 102 | 103 | # PyBuilder 104 | target/ 105 | 106 | # Jupyter Notebook 107 | 108 | # pyenv 109 | .python-version 110 | 111 | # celery beat schedule file 112 | celerybeat-schedule 113 | 114 | # SageMath parsed files 115 | *.sage.py 116 | 117 | # Environments 118 | .env 119 | .venv 120 | env/ 121 | venv/ 122 | ENV/ 123 | env.bak/ 124 | venv.bak/ 125 | 126 | # Spyder project settings 127 | .spyderproject 128 | .spyproject 129 | 130 | # Rope project settings 131 | .ropeproject 132 | 133 | # mkdocs documentation 134 | /site 135 | 136 | # mypy 137 | .mypy_cache/ 138 | 139 | # End of https://www.gitignore.io/api/macos,python,jupyternotebook -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Oliver Steele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | keyring = "*" 11 | ipython = "*" 12 | 13 | 14 | [dev-packages] 15 | 16 | pytest = "*" 17 | bumpversion = "*" 18 | sphinx = "*" 19 | sphinx-rtd-theme = "*" 20 | tox = "*" 21 | flit = "*" 22 | ipython = "*" 23 | pytest-watch = "*" 24 | 25 | 26 | [requires] 27 | 28 | python_version = "3.6" 29 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "d818d576fd338414b23f75bf63f1f792f2852424bea323688dc8fc9a71764858" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "appnope": { 20 | "hashes": [ 21 | "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", 22 | "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" 23 | ], 24 | "markers": "sys_platform == 'darwin'", 25 | "version": "==0.1.0" 26 | }, 27 | "backcall": { 28 | "hashes": [ 29 | "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", 30 | "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" 31 | ], 32 | "version": "==0.1.0" 33 | }, 34 | "colorama": { 35 | "hashes": [ 36 | "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", 37 | "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" 38 | ], 39 | "markers": "sys_platform == 'win32'", 40 | "version": "==0.3.9" 41 | }, 42 | "decorator": { 43 | "hashes": [ 44 | "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", 45 | "sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c" 46 | ], 47 | "version": "==4.3.0" 48 | }, 49 | "entrypoints": { 50 | "hashes": [ 51 | "sha256:10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b", 52 | "sha256:d2d587dde06f99545fb13a383d2cd336a8ff1f359c5839ce3a64c917d10c029f" 53 | ], 54 | "version": "==0.2.3" 55 | }, 56 | "enum34": { 57 | "hashes": [ 58 | "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", 59 | "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", 60 | "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", 61 | "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" 62 | ], 63 | "markers": "python_version == '2.7'", 64 | "version": "==1.1.6" 65 | }, 66 | "ipython": { 67 | "hashes": [ 68 | "sha256:a0c96853549b246991046f32d19db7140f5b1a644cc31f0dc1edc86713b7676f", 69 | "sha256:eca537aa61592aca2fef4adea12af8e42f5c335004dfa80c78caf80e8b525e5c" 70 | ], 71 | "version": "==6.4.0" 72 | }, 73 | "ipython-genutils": { 74 | "hashes": [ 75 | "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", 76 | "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" 77 | ], 78 | "version": "==0.2.0" 79 | }, 80 | "jedi": { 81 | "hashes": [ 82 | "sha256:1972f694c6bc66a2fac8718299e2ab73011d653a6d8059790c3476d2353b99ad", 83 | "sha256:5861f6dc0c16e024cbb0044999f9cf8013b292c05f287df06d3d991a87a4eb89" 84 | ], 85 | "version": "==0.12.0" 86 | }, 87 | "keyring": { 88 | "hashes": [ 89 | "sha256:4498eaa2e32fc69a8b36749116b670c379d36a1a9ad4ab107df1e19c8a120ffe", 90 | "sha256:fd597e72df7240ec5a4215c50957e41c3d4bd321d97bf163f4a8e75ca287d77b" 91 | ], 92 | "version": "==12.2.1" 93 | }, 94 | "parso": { 95 | "hashes": [ 96 | "sha256:cdef26e8adc10d589f3ec4eb444bd0a29f3f1eb6d72a4292ab8afcb9d68976a6", 97 | "sha256:f0604a40b96e062b0fd99cf134cc2d5cdf66939d0902f8267d938b0d5b26707f" 98 | ], 99 | "version": "==0.2.1" 100 | }, 101 | "pathlib2": { 102 | "hashes": [ 103 | "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", 104 | "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" 105 | ], 106 | "markers": "python_version in '2.6 2.7 3.2 3.3'", 107 | "version": "==2.3.2" 108 | }, 109 | "pexpect": { 110 | "hashes": [ 111 | "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", 112 | "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" 113 | ], 114 | "markers": "sys_platform != 'win32'", 115 | "version": "==4.6.0" 116 | }, 117 | "pickleshare": { 118 | "hashes": [ 119 | "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", 120 | "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" 121 | ], 122 | "version": "==0.7.4" 123 | }, 124 | "prompt-toolkit": { 125 | "hashes": [ 126 | "sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", 127 | "sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", 128 | "sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917" 129 | ], 130 | "version": "==1.0.15" 131 | }, 132 | "ptyprocess": { 133 | "hashes": [ 134 | "sha256:e64193f0047ad603b71f202332ab5527c5e52aa7c8b609704fc28c0dc20c4365", 135 | "sha256:e8c43b5eee76b2083a9badde89fd1bbce6c8942d1045146e100b7b5e014f4f1a" 136 | ], 137 | "version": "==0.5.2" 138 | }, 139 | "pygments": { 140 | "hashes": [ 141 | "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", 142 | "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" 143 | ], 144 | "version": "==2.2.0" 145 | }, 146 | "scandir": { 147 | "hashes": [ 148 | "sha256:24f32112c483ac6c4a40b62f1282e61ecca7977153b66a0d26a9583a716dcb64", 149 | "sha256:6c80092f8fe3e62c3da3110067589c6661c722b0889906d2974e5150f1314523", 150 | "sha256:7729b8444c5f5187649ff58501e7c2ad22b84d7dc28f738f64c5b615913fec22", 151 | "sha256:8e3ca5925cc13787aeafbf08f055a8066c091fc20bfa8783235b916cf047afbe", 152 | "sha256:96dfc553f50946deb6d1cd762bac5cf122832c4aa253c885ca357ef53dd8d072", 153 | "sha256:b2d55be869c4f716084a19b1e16932f0769711316ba62de941320bf2be84763d", 154 | "sha256:b55a091b91f9e6c9c7129889b2f58df329530172a99172de9e784545342a45e6", 155 | "sha256:b6cb611a18a828146a178362a36a2c6557c51c596ded4314cb516dd8c947b4ce", 156 | "sha256:d985e36eb3effebb20434e6cd7495440b4ba676a22f3ec61e9fff9f3e2995238", 157 | "sha256:f39dd5affde2860fb28176d2233f318ccca97c55019407ee8172b3fba0b211db", 158 | "sha256:f91418e82edb5a43b020fa15e30a41d730b71c5957536749366bf63cc05427b1" 159 | ], 160 | "markers": "python_version < '3.5'", 161 | "version": "==1.7" 162 | }, 163 | "simplegeneric": { 164 | "hashes": [ 165 | "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" 166 | ], 167 | "version": "==0.8.1" 168 | }, 169 | "six": { 170 | "hashes": [ 171 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 172 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 173 | ], 174 | "version": "==1.11.0" 175 | }, 176 | "traitlets": { 177 | "hashes": [ 178 | "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", 179 | "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" 180 | ], 181 | "version": "==4.3.2" 182 | }, 183 | "typing": { 184 | "hashes": [ 185 | "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", 186 | "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", 187 | "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" 188 | ], 189 | "markers": "python_version <= '3.4'", 190 | "version": "==3.6.4" 191 | }, 192 | "wcwidth": { 193 | "hashes": [ 194 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 195 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 196 | ], 197 | "version": "==0.1.7" 198 | }, 199 | "win-unicode-console": { 200 | "hashes": [ 201 | "sha256:d4142d4d56d46f449d6f00536a73625a871cba040f0bc1a2e305a04578f07d1e" 202 | ], 203 | "markers": "sys_platform == 'win32' and python_version < '3.6'", 204 | "version": "==0.5" 205 | } 206 | }, 207 | "develop": { 208 | "alabaster": { 209 | "hashes": [ 210 | "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732", 211 | "sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0" 212 | ], 213 | "version": "==0.7.10" 214 | }, 215 | "appnope": { 216 | "hashes": [ 217 | "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", 218 | "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" 219 | ], 220 | "markers": "sys_platform == 'darwin'", 221 | "version": "==0.1.0" 222 | }, 223 | "argh": { 224 | "hashes": [ 225 | "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3", 226 | "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65" 227 | ], 228 | "version": "==0.26.2" 229 | }, 230 | "atomicwrites": { 231 | "hashes": [ 232 | "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", 233 | "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" 234 | ], 235 | "version": "==1.1.5" 236 | }, 237 | "attrs": { 238 | "hashes": [ 239 | "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", 240 | "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" 241 | ], 242 | "version": "==18.1.0" 243 | }, 244 | "babel": { 245 | "hashes": [ 246 | "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", 247 | "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" 248 | ], 249 | "version": "==2.6.0" 250 | }, 251 | "backcall": { 252 | "hashes": [ 253 | "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", 254 | "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" 255 | ], 256 | "version": "==0.1.0" 257 | }, 258 | "bumpversion": { 259 | "hashes": [ 260 | "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", 261 | "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57" 262 | ], 263 | "version": "==0.5.3" 264 | }, 265 | "certifi": { 266 | "hashes": [ 267 | "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", 268 | "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" 269 | ], 270 | "version": "==2018.4.16" 271 | }, 272 | "chardet": { 273 | "hashes": [ 274 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 275 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 276 | ], 277 | "version": "==3.0.4" 278 | }, 279 | "colorama": { 280 | "hashes": [ 281 | "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", 282 | "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" 283 | ], 284 | "markers": "sys_platform == 'win32'", 285 | "version": "==0.3.9" 286 | }, 287 | "decorator": { 288 | "hashes": [ 289 | "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", 290 | "sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c" 291 | ], 292 | "version": "==4.3.0" 293 | }, 294 | "docopt": { 295 | "hashes": [ 296 | "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" 297 | ], 298 | "version": "==0.6.2" 299 | }, 300 | "docutils": { 301 | "hashes": [ 302 | "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", 303 | "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", 304 | "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" 305 | ], 306 | "version": "==0.14" 307 | }, 308 | "enum34": { 309 | "hashes": [ 310 | "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", 311 | "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", 312 | "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", 313 | "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" 314 | ], 315 | "markers": "python_version == '2.7'", 316 | "version": "==1.1.6" 317 | }, 318 | "flit": { 319 | "hashes": [ 320 | "sha256:5eba92b254f198e534855919c1596d68bb46a7ee2adab60037df6268672dba38", 321 | "sha256:95b8577b2232da39ee14ae237575b7a85afeeabc1e87f4a19485fac34f85aa89" 322 | ], 323 | "version": "==1.0" 324 | }, 325 | "idna": { 326 | "hashes": [ 327 | "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", 328 | "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" 329 | ], 330 | "version": "==2.7" 331 | }, 332 | "imagesize": { 333 | "hashes": [ 334 | "sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18", 335 | "sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315" 336 | ], 337 | "version": "==1.0.0" 338 | }, 339 | "ipython": { 340 | "hashes": [ 341 | "sha256:a0c96853549b246991046f32d19db7140f5b1a644cc31f0dc1edc86713b7676f", 342 | "sha256:eca537aa61592aca2fef4adea12af8e42f5c335004dfa80c78caf80e8b525e5c" 343 | ], 344 | "version": "==6.4.0" 345 | }, 346 | "ipython-genutils": { 347 | "hashes": [ 348 | "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", 349 | "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" 350 | ], 351 | "version": "==0.2.0" 352 | }, 353 | "jedi": { 354 | "hashes": [ 355 | "sha256:1972f694c6bc66a2fac8718299e2ab73011d653a6d8059790c3476d2353b99ad", 356 | "sha256:5861f6dc0c16e024cbb0044999f9cf8013b292c05f287df06d3d991a87a4eb89" 357 | ], 358 | "version": "==0.12.0" 359 | }, 360 | "jinja2": { 361 | "hashes": [ 362 | "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", 363 | "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" 364 | ], 365 | "version": "==2.10" 366 | }, 367 | "markupsafe": { 368 | "hashes": [ 369 | "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" 370 | ], 371 | "version": "==1.0" 372 | }, 373 | "more-itertools": { 374 | "hashes": [ 375 | "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8", 376 | "sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3", 377 | "sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0" 378 | ], 379 | "version": "==4.2.0" 380 | }, 381 | "packaging": { 382 | "hashes": [ 383 | "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", 384 | "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" 385 | ], 386 | "version": "==17.1" 387 | }, 388 | "parso": { 389 | "hashes": [ 390 | "sha256:cdef26e8adc10d589f3ec4eb444bd0a29f3f1eb6d72a4292ab8afcb9d68976a6", 391 | "sha256:f0604a40b96e062b0fd99cf134cc2d5cdf66939d0902f8267d938b0d5b26707f" 392 | ], 393 | "version": "==0.2.1" 394 | }, 395 | "pathlib2": { 396 | "hashes": [ 397 | "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", 398 | "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" 399 | ], 400 | "markers": "python_version in '2.6 2.7 3.2 3.3'", 401 | "version": "==2.3.2" 402 | }, 403 | "pathtools": { 404 | "hashes": [ 405 | "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0" 406 | ], 407 | "version": "==0.1.2" 408 | }, 409 | "pexpect": { 410 | "hashes": [ 411 | "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", 412 | "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" 413 | ], 414 | "markers": "sys_platform != 'win32'", 415 | "version": "==4.6.0" 416 | }, 417 | "pickleshare": { 418 | "hashes": [ 419 | "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", 420 | "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" 421 | ], 422 | "version": "==0.7.4" 423 | }, 424 | "pluggy": { 425 | "hashes": [ 426 | "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", 427 | "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", 428 | "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" 429 | ], 430 | "version": "==0.6.0" 431 | }, 432 | "prompt-toolkit": { 433 | "hashes": [ 434 | "sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", 435 | "sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", 436 | "sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917" 437 | ], 438 | "version": "==1.0.15" 439 | }, 440 | "ptyprocess": { 441 | "hashes": [ 442 | "sha256:e64193f0047ad603b71f202332ab5527c5e52aa7c8b609704fc28c0dc20c4365", 443 | "sha256:e8c43b5eee76b2083a9badde89fd1bbce6c8942d1045146e100b7b5e014f4f1a" 444 | ], 445 | "version": "==0.5.2" 446 | }, 447 | "py": { 448 | "hashes": [ 449 | "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", 450 | "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" 451 | ], 452 | "version": "==1.5.3" 453 | }, 454 | "pygments": { 455 | "hashes": [ 456 | "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", 457 | "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" 458 | ], 459 | "version": "==2.2.0" 460 | }, 461 | "pyparsing": { 462 | "hashes": [ 463 | "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", 464 | "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" 465 | ], 466 | "version": "==2.2.0" 467 | }, 468 | "pytest": { 469 | "hashes": [ 470 | "sha256:26838b2bc58620e01675485491504c3aa7ee0faf335c37fcd5f8731ca4319591", 471 | "sha256:32c49a69566aa7c333188149ad48b58ac11a426d5352ea3d8f6ce843f88199cb" 472 | ], 473 | "version": "==3.6.1" 474 | }, 475 | "pytest-watch": { 476 | "hashes": [ 477 | "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9" 478 | ], 479 | "version": "==4.2.0" 480 | }, 481 | "pytoml": { 482 | "hashes": [ 483 | "sha256:459cb4b9ced919cefd4d4587498b3379c37924be7aa9ee104e49337a0174ab79" 484 | ], 485 | "version": "==0.1.16" 486 | }, 487 | "pytz": { 488 | "hashes": [ 489 | "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", 490 | "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" 491 | ], 492 | "version": "==2018.4" 493 | }, 494 | "pyyaml": { 495 | "hashes": [ 496 | "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", 497 | "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", 498 | "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", 499 | "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", 500 | "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", 501 | "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", 502 | "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", 503 | "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" 504 | ], 505 | "version": "==3.12" 506 | }, 507 | "requests": { 508 | "hashes": [ 509 | "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", 510 | "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" 511 | ], 512 | "version": "==2.19.1" 513 | }, 514 | "requests-download": { 515 | "hashes": [ 516 | "sha256:92d895a6ca51ea51aa42bab864bddaee31b5601c7e7e1ade4c27b0eb6695d846", 517 | "sha256:994d9d332befae6616f562769bab163f08d6404dc7e28fb7bfed4a0a43a754ad" 518 | ], 519 | "version": "==0.1.2" 520 | }, 521 | "scandir": { 522 | "hashes": [ 523 | "sha256:24f32112c483ac6c4a40b62f1282e61ecca7977153b66a0d26a9583a716dcb64", 524 | "sha256:6c80092f8fe3e62c3da3110067589c6661c722b0889906d2974e5150f1314523", 525 | "sha256:7729b8444c5f5187649ff58501e7c2ad22b84d7dc28f738f64c5b615913fec22", 526 | "sha256:8e3ca5925cc13787aeafbf08f055a8066c091fc20bfa8783235b916cf047afbe", 527 | "sha256:96dfc553f50946deb6d1cd762bac5cf122832c4aa253c885ca357ef53dd8d072", 528 | "sha256:b2d55be869c4f716084a19b1e16932f0769711316ba62de941320bf2be84763d", 529 | "sha256:b55a091b91f9e6c9c7129889b2f58df329530172a99172de9e784545342a45e6", 530 | "sha256:b6cb611a18a828146a178362a36a2c6557c51c596ded4314cb516dd8c947b4ce", 531 | "sha256:d985e36eb3effebb20434e6cd7495440b4ba676a22f3ec61e9fff9f3e2995238", 532 | "sha256:f39dd5affde2860fb28176d2233f318ccca97c55019407ee8172b3fba0b211db", 533 | "sha256:f91418e82edb5a43b020fa15e30a41d730b71c5957536749366bf63cc05427b1" 534 | ], 535 | "markers": "python_version < '3.5'", 536 | "version": "==1.7" 537 | }, 538 | "simplegeneric": { 539 | "hashes": [ 540 | "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" 541 | ], 542 | "version": "==0.8.1" 543 | }, 544 | "six": { 545 | "hashes": [ 546 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 547 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 548 | ], 549 | "version": "==1.11.0" 550 | }, 551 | "snowballstemmer": { 552 | "hashes": [ 553 | "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", 554 | "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" 555 | ], 556 | "version": "==1.2.1" 557 | }, 558 | "sphinx": { 559 | "hashes": [ 560 | "sha256:85f7e32c8ef07f4ba5aeca728e0f7717bef0789fba8458b8d9c5c294cad134f3", 561 | "sha256:d45480a229edf70d84ca9fae3784162b1bc75ee47e480ffe04a4b7f21a95d76d" 562 | ], 563 | "version": "==1.7.5" 564 | }, 565 | "sphinx-rtd-theme": { 566 | "hashes": [ 567 | "sha256:aa3e190392e963551432de7df24b8a5fbe5b71a2f4fcd9d5b75808b52ad999e5", 568 | "sha256:de88d637a60371d4f923e06b79c4ba260490c57d2ab5a8316942ab5d9a6ce1bf" 569 | ], 570 | "version": "==0.4.0" 571 | }, 572 | "sphinxcontrib-websupport": { 573 | "hashes": [ 574 | "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", 575 | "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" 576 | ], 577 | "version": "==1.1.0" 578 | }, 579 | "tox": { 580 | "hashes": [ 581 | "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", 582 | "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" 583 | ], 584 | "version": "==3.0.0" 585 | }, 586 | "traitlets": { 587 | "hashes": [ 588 | "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", 589 | "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" 590 | ], 591 | "version": "==4.3.2" 592 | }, 593 | "typing": { 594 | "hashes": [ 595 | "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", 596 | "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", 597 | "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" 598 | ], 599 | "markers": "python_version <= '3.4'", 600 | "version": "==3.6.4" 601 | }, 602 | "urllib3": { 603 | "hashes": [ 604 | "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", 605 | "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" 606 | ], 607 | "version": "==1.23" 608 | }, 609 | "virtualenv": { 610 | "hashes": [ 611 | "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", 612 | "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" 613 | ], 614 | "version": "==16.0.0" 615 | }, 616 | "watchdog": { 617 | "hashes": [ 618 | "sha256:7e65882adb7746039b6f3876ee174952f8eaaa34491ba34333ddf1fe35de4162" 619 | ], 620 | "version": "==0.8.3" 621 | }, 622 | "wcwidth": { 623 | "hashes": [ 624 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 625 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 626 | ], 627 | "version": "==0.1.7" 628 | }, 629 | "win-unicode-console": { 630 | "hashes": [ 631 | "sha256:d4142d4d56d46f449d6f00536a73625a871cba040f0bc1a2e305a04578f07d1e" 632 | ], 633 | "markers": "sys_platform == 'win32' and python_version < '3.6'", 634 | "version": "==0.5" 635 | }, 636 | "zipfile36": { 637 | "hashes": [ 638 | "sha256:a78a8dddf4fa114f7fe73df76ffcce7538e23433b7a6a96c1c904023f122aead", 639 | "sha256:f7e48adf627f75cd74cdb50e7d850623b465f4cf5de913809196e8f3aa57dc4b" 640 | ], 641 | "markers": "python_version in '3.3 3.4 3.5'", 642 | "version": "==0.1.3" 643 | } 644 | } 645 | } 646 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | IPython Secrets 2 | =============== 3 | 4 | |PyPI version| |Doc Status| |License| |Supported Python| 5 | 6 | **IPython Secrets** makes it easier to use secrets in a Jupyter notebook. 7 | 8 | The first time `get_secret` is called, it prompts the user for the password or 9 | other secret. After this value is read, it is saved in the system keyring, and 10 | the Jupyter output cell is then cleared. 11 | 12 | |gif1| 13 | 14 | Subsequent calls to ``get_secret`` use the saved value. This is true even 15 | if they are called in a different process running in the same account -- for 16 | example, if the machine has been rebooted, or the local Jupyter server has 17 | otherwise been shut down and restarted. 18 | 19 | |gif2| 20 | 21 | .. note:: Install the `gsheet-keyring package`_ to use ipython-secrets on 22 | `Google Colaboratory`_: ``pip3 install gsheet-keyring``. The gsheet-keyring 23 | package provides a Keyring backend that is backed by Google Sheets. This is 24 | necessary since the Colaboratory environment doesn't provide a persistent 25 | file system, or the OS services that the Keyring's standard and alternative 26 | backends rely on. 27 | 28 | .. warning:: This package stores the secret on the Jupyter server. Don't trust 29 | it with secrets you don't trust the Jupyter server with. (This is true for 30 | all means of using a secret in a notebook.) 31 | 32 | .. warning:: The package is intended to reduce the likelihood of accidental 33 | disclosure of secrets in notebook source. It won't secure a secret from code 34 | that is running *in* the notebook; and it won't keep you from writing code 35 | that displays the secret in a notebook output cell -- in which case it has 36 | been disclosed to whoever can see the notebook. 37 | 38 | Install 39 | ------- 40 | 41 | .. code:: bash 42 | 43 | pip3 install ipython-secrets 44 | 45 | Usage 46 | ----- 47 | 48 | .. code:: python 49 | 50 | from ipython_secrets import * 51 | 52 | TWILIO_API_KEY = get_secret('TWILIO_API_KEY') 53 | 54 | Alternatives 55 | ------------ 56 | 57 | Secrets can also be stored in an environment variable, and read from the 58 | notebook. This is a best practice for applications (and especially web and other 59 | `server-side services`_), but I've found it inconvenient for notebooks -- the 60 | notebook server must be re-started to pick up a new environment variable; and, 61 | it complicates the setup instructions for notebook users. 62 | 63 | Development 64 | ----------- 65 | 66 | Install Pipenv, and required packages: 67 | 68 | .. code:: bash 69 | 70 | $ pip3 install pipenv 71 | $ pipenv install 72 | $ pipenv shell 73 | $ pip install flit 74 | 75 | Install locally: 76 | 77 | .. code:: bash 78 | 79 | flit install --symlink 80 | 81 | Acknowledgements 82 | ---------------- 83 | 84 | This package is a thin wrapper around Keyring_. 85 | 86 | License 87 | ------- 88 | 89 | MIT 90 | 91 | .. |PyPI version| image:: https://img.shields.io/pypi/v/ipython-secrets.svg 92 | :target: https://pypi.python.org/pypi/ipython-secrets 93 | :alt: Latest PyPI Version 94 | .. |Doc Status| image:: https://readthedocs.org/projects/ipython-secrets/badge/?version=latest 95 | :target: http://ipython-secrets.readthedocs.io/en/latest/?badge=latest 96 | :alt: Documentation Status 97 | .. |License| image:: https://img.shields.io/pypi/l/ipython-secrets.svg 98 | :target: https://pypi.python.org/pypi/ipython-secrets 99 | :alt: License 100 | .. |Supported Python| image:: https://img.shields.io/pypi/pyversions/ipython-secrets.svg 101 | :target: https://pypi.python.org/pypi/ipython-secrets 102 | :alt: Supported Python Versions 103 | 104 | .. _API documentation: http://ipython-secrets.readthedocs.io/en/latest/?badge=latest#module-ipython_secrets 105 | 106 | .. |gif1| image:: ./docs/images/first-time.gif 107 | .. |gif2| image:: ./docs/images/next-time.gif 108 | 109 | .. _Google Colaboratory: https://colab.research.google.com/ 110 | .. _Hydrogen: https://nteract.io/atom 111 | .. _Keyring: https://pypi.python.org/pypi/keyring 112 | .. _Nteract: https://nteract.io 113 | .. _server-side services: https://12factor.net/ 114 | .. _gsheet-keyring package: https://pypi.org/project/gsheet-keyring/ 115 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import keyring 3 | import pytest 4 | from keyring.errors import PasswordDeleteError 5 | 6 | os.environ['USER'] = 'ipython_secrets_PYTEST_USER' 7 | 8 | 9 | class MockKeyringBackend(keyring.backend.KeyringBackend): 10 | _passwords = dict() 11 | 12 | def clear(self): 13 | self._passwords.clear() 14 | 15 | def get_password(self, servicename, username): 16 | return self._passwords.get((servicename, username)) 17 | 18 | def set_password(self, servicename, username, password): 19 | self._passwords[(servicename, username)] = password 20 | 21 | def delete_password(self, servicename, username): 22 | try: 23 | self._passwords.pop((servicename, username)) 24 | except IndexError: 25 | raise PasswordDeleteError() 26 | 27 | 28 | mockKeyringBackend = MockKeyringBackend() 29 | keyring.set_keyring(mockKeyringBackend) 30 | 31 | 32 | @pytest.fixture 33 | def keyring_backend(): 34 | mockKeyringBackend.clear() 35 | return mockKeyringBackend 36 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = ipython_secrets 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ipython_secrets documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Jan 22 08:06:00 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.napoleon', 36 | 'sphinx.ext.viewcode', 37 | 'sphinx.ext.intersphinx'] 38 | 39 | intersphinx_mapping = { 40 | 'python': ('https://docs.python.org/3.6', None), 41 | 'keyring': ('http://keyring.readthedocs.io/en/latest', None), 42 | } 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # The suffix(es) of source filenames. 48 | # You can specify multiple suffix as a list of string: 49 | # 50 | # source_suffix = ['.rst', '.md'] 51 | source_suffix = '.rst' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = 'ipython_secrets' 58 | copyright = '2018, Oliver Steele' 59 | author = 'Oliver Steele' 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = '1.1.1' 67 | # The full version, including alpha/beta/rc tags. 68 | release = version 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | # This patterns also effect to html_static_path and html_extra_path 80 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 81 | 82 | # The name of the Pygments (syntax highlighting) style to use. 83 | pygments_style = 'sphinx' 84 | 85 | # If true, `todo` and `todoList` produce output, else they produce nothing. 86 | todo_include_todos = False 87 | 88 | 89 | # -- Options for HTML output ---------------------------------------------- 90 | 91 | # The theme to use for HTML and HTML Help pages. See the documentation for 92 | # a list of builtin themes. 93 | # 94 | html_theme = 'sphinx_rtd_theme' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | # 100 | # html_theme_options = {} 101 | 102 | # Add any paths that contain custom static files (such as style sheets) here, 103 | # relative to this directory. They are copied after the builtin static files, 104 | # so a file named "default.css" will overwrite the builtin "default.css". 105 | html_static_path = ['_static'] 106 | 107 | 108 | # -- Options for HTMLHelp output ------------------------------------------ 109 | 110 | # Output file base name for HTML help builder. 111 | htmlhelp_basename = 'ipython_secretsdoc' 112 | 113 | 114 | # -- Options for LaTeX output --------------------------------------------- 115 | 116 | latex_elements = { 117 | # The paper size ('letterpaper' or 'a4paper'). 118 | # 119 | # 'papersize': 'letterpaper', 120 | 121 | # The font size ('10pt', '11pt' or '12pt'). 122 | # 123 | # 'pointsize': '10pt', 124 | 125 | # Additional stuff for the LaTeX preamble. 126 | # 127 | # 'preamble': '', 128 | 129 | # Latex figure (float) alignment 130 | # 131 | # 'figure_align': 'htbp', 132 | } 133 | 134 | # Grouping the document tree into LaTeX files. List of tuples 135 | # (source start file, target name, title, 136 | # author, documentclass [howto, manual, or own class]). 137 | latex_documents = [ 138 | (master_doc, 'ipython_secrets.tex', 'ipython_secrets Documentation', 139 | 'Oliver Steele', 'manual'), 140 | ] 141 | 142 | 143 | # -- Options for manual page output --------------------------------------- 144 | 145 | # One entry per manual page. List of tuples 146 | # (source start file, name, description, authors, manual section). 147 | man_pages = [ 148 | (master_doc, 'ipython_secrets', 'ipython_secrets Documentation', 149 | [author], 1) 150 | ] 151 | 152 | 153 | # -- Options for Texinfo output ------------------------------------------- 154 | 155 | # Grouping the document tree into Texinfo files. List of tuples 156 | # (source start file, target name, title, author, 157 | # dir menu entry, description, category) 158 | texinfo_documents = [ 159 | (master_doc, 'ipython_secrets', 'ipython_secrets Documentation', 160 | author, 'ipython_secrets', 'One line description of project.', 161 | 'Miscellaneous'), 162 | ] 163 | -------------------------------------------------------------------------------- /docs/docs/images: -------------------------------------------------------------------------------- 1 | ../images -------------------------------------------------------------------------------- /docs/images/first-time.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteele/ipython-secrets/0b707165621262bf9ebd7cd3fd82835aeb179f26/docs/images/first-time.gif -------------------------------------------------------------------------------- /docs/images/next-time.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteele/ipython-secrets/0b707165621262bf9ebd7cd3fd82835aeb179f26/docs/images/next-time.gif -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | .. toctree:: 4 | :maxdepth: 2 5 | :caption: Contents: 6 | 7 | 8 | API 9 | --- 10 | 11 | .. automodule:: ipython_secrets 12 | :members: 13 | -------------------------------------------------------------------------------- /ipython_secrets.py: -------------------------------------------------------------------------------- 1 | """This package provides functions for using secrets in a Jupyter notebook. 2 | 3 | These functions are for use in a notebook that needs to make use of secrets, 4 | such as passwords and API keys, to avoid storing the secret in the notebook 5 | source. 6 | 7 | .. note:: This package uses Keyring_. See the `Keyring API documentation`_ for 8 | additional information about where secrets are stored, and how to change the 9 | default location. 10 | 11 | .. _Keyring: https://pypi.python.org/pypi/keyring 12 | .. _Keyring API documentation: http://keyring.readthedocs.io/en/latest/?badge=latest 13 | """ 14 | 15 | 16 | __version__ = '1.1.1' 17 | 18 | import os 19 | import keyring 20 | try: 21 | from IPython.display import clear_output 22 | except ImportError: 23 | def clear_output(): 24 | pass 25 | 26 | __all__ = ["get_secret", "set_secret", "delete_secret"] 27 | 28 | DEFAULT = object() 29 | 30 | 31 | def _iter_usernames(): 32 | """Yield username candidates. 33 | 34 | Try a few heuristics for determining the username: 35 | 36 | 1. The value of the USER environment variable. 37 | 2. If the keyring backend has a `credentials` property 38 | (:class:`gsheet_keyring:GoogleSheetKeyring` does): 39 | 40 | 1. The value of ``credentials.id_token['email']`, if this is defined. 41 | 2. The user info email returned by the Google OAuth2 service. 42 | 43 | 3. If the oauth2 library is available, get the application default 44 | credentials, and repeat 2.1–2.2 with these credentials. 45 | """ 46 | yield os.environ.get('USER') 47 | 48 | def iter_credential_usernames(credentials): 49 | try: 50 | if credentials.id_token: 51 | yield credentials.id_token['email'] 52 | except (AttributeError, KeyError): 53 | pass 54 | 55 | try: 56 | from googleapiclient.discovery import build 57 | service = build(serviceName='oauth2', version='v2', credentials=credentials) 58 | info = service.userinfo().get().execute() 59 | yield info['email'] 60 | except (AttributeError, ImportError): 61 | pass 62 | 63 | def iter_credentials(): 64 | try: 65 | yield keyring.get_keyring().credentials 66 | except AttributeError: 67 | pass 68 | 69 | try: 70 | from oauth2client.client import (ApplicationDefaultCredentialsError, 71 | GoogleCredentials) 72 | yield GoogleCredentials.get_application_default() 73 | except (ApplicationDefaultCredentialsError, ImportError): 74 | pass 75 | 76 | for credentials in iter_credentials(): 77 | yield from iter_credential_usernames(credentials) 78 | 79 | 80 | def get_username(): 81 | """Return a default username, using a few heuristics. 82 | 83 | Returns the first username from the ``USER`` environment variable , the 84 | current keyring backend's OAuth2 ``credentials``, and (if 85 | :mod:`oauth2client` is installed) the current environnment's `Application 86 | Default Credentials`_. 87 | 88 | .. _Application Default Credentials: 89 | https://cloud.google.com/docs/authentication/production 90 | """ 91 | return next(filter(None, _iter_usernames()), 'ipython-secrets') 92 | 93 | 94 | def get_secret(servicename, *, username=None, 95 | default=DEFAULT, force_prompt=False, prompt=None): 96 | """Read a stored secret, or prompt the user for its value. 97 | 98 | Look for a secret in the keyring. If it's not present, prompt the user, 99 | clear the cell, and save the secret. 100 | 101 | Parameters 102 | ---------- 103 | servicename: str 104 | A keyring service name. 105 | 106 | username: str, optional 107 | A keyring username. This defaults to the value of the USER environment 108 | variable. (Note that this can programmatically altered.) 109 | 110 | default: str, optional 111 | The default value, if the secret is not present in the keyring. If this 112 | is supplied, the user is never prompted. 113 | 114 | force_prompt: str, optional 115 | If true, the user is always prompted for a secret. 116 | 117 | prompt: str, optional 118 | The text displayed to the user as part of the prompt. 119 | 120 | Examples 121 | -------- 122 | :: 123 | 124 | from ipython_secrets import * 125 | 126 | TWILIO_API_KEY = get_secret('TWILIO_API_KEY') 127 | TWILIO_API_KEY = get_secret('TWILIO_API_KEY', 'my-account') 128 | TWILIO_API_KEY = get_secret('TWILIO_API_KEY', 'my-account', 129 | prompt="Enter the API key") 130 | """ 131 | if username is None: 132 | username = get_username() 133 | password = None 134 | if not force_prompt: 135 | password = keyring.get_password(servicename, username) 136 | if password is not None: 137 | return password 138 | if default is not DEFAULT: 139 | return default 140 | prompt = '{}[{}]'.format(servicename, username) if prompt is None else prompt 141 | password = input(prompt) 142 | keyring.set_password(servicename, username, password) 143 | clear_output() 144 | return password 145 | 146 | 147 | def set_secret(servicename, password, *, username=None): 148 | """Set a secret value. 149 | 150 | Parameters 151 | ---------- 152 | servicename: str 153 | A keyring service name. 154 | 155 | password: str 156 | A keyring service name. 157 | 158 | username: str, optional 159 | A keyring username. This defaults to the value of the USER environment 160 | variable. 161 | 162 | Notes 163 | ----- 164 | The argument order to `set_secret` is different from 165 | :func:`keyring.set_password`, and `username` can only be used as keyword 166 | parameter. This is in order that `username` can be optional, for 167 | compatibility with the more-frequently-used functions in this package. 168 | 169 | """ 170 | keyring.set_password(servicename, username or get_username(), password) 171 | 172 | 173 | def delete_secret(servicename, username=None): 174 | """Delete a secret from the keyring. 175 | 176 | Parameters 177 | ---------- 178 | servicename: str 179 | A keyring service name. 180 | 181 | username: str, optional 182 | A keyring username. This defaults to the value of the USER environment 183 | variable. 184 | """ 185 | keyring.delete_password(servicename, username or get_username()) 186 | -------------------------------------------------------------------------------- /ipython_secrets_test.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | from ipython_secrets import delete_secret, get_secret, set_secret 4 | 5 | 6 | def test_get_secret(keyring_backend): 7 | with patch('ipython_secrets.input', lambda *x: 'user input'): 8 | assert get_secret('KEY') == 'user input' 9 | 10 | 11 | def test_get_secret_defaults(keyring_backend): 12 | assert get_secret('KEY', default='default') == 'default' 13 | assert get_secret('KEY', default=None) is None 14 | 15 | set_secret('KEY', 'S1') 16 | assert get_secret('KEY', default='default') == 'S1' 17 | assert get_secret('KEY', default=None) == 'S1' 18 | 19 | 20 | def test_set_secret(keyring_backend): 21 | set_secret('K1', 'S1') 22 | assert get_secret('K1') == 'S1' 23 | 24 | set_secret('K2', 'S2') 25 | assert get_secret('K1') == 'S1' 26 | assert get_secret('K2') == 'S2' 27 | 28 | set_secret('K1', 'S3') 29 | assert get_secret('K1') == 'S3' 30 | assert get_secret('K2') == 'S2' 31 | 32 | set_secret('K1', 'S4', username='u1') 33 | set_secret('K1', 'S5', username='u2') 34 | assert get_secret('K1') == 'S3' 35 | assert get_secret('K1', username='u1') == 'S4' 36 | assert get_secret('K1', username='u2') == 'S5' 37 | 38 | 39 | def test_delete_secret(keyring_backend): 40 | set_secret('K1', 'S1') 41 | set_secret('K2', 'S2') 42 | delete_secret('K1') 43 | assert get_secret('K1', default='none') is 'none' 44 | assert get_secret('K2') == 'S2' 45 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit"] 3 | build-backend = "flit.buildapi" 4 | 5 | [tool.flit.metadata] 6 | dist-name = "ipython-secrets" 7 | module = "ipython_secrets" 8 | author = "Oliver Steele" 9 | author-email = "steele@osteele.com" 10 | home-page = "https://github.com/osteele/ipython-secrets" 11 | classifiers = [ 12 | "Framework :: IPython", 13 | "Framework :: Jupyter", 14 | "Intended Audience :: Developers", 15 | "License :: OSI Approved :: MIT License", 16 | "Operating System :: OS Independent", 17 | "Programming Language :: Python :: 3.6", 18 | "Programming Language :: Python", 19 | ] 20 | requires = ["keyring" , "ipython"] 21 | requires-python = ">=3.5" 22 | dev-requires = [ 23 | "bumpversion", 24 | "Sphinx", 25 | "sphinx_rtd_theme", 26 | ] 27 | description-file = "./README.rst" 28 | 29 | [tool.flit.metadata.urls] 30 | Documentation = "https://ipython-secrets.readthedocs.io/en/latest/" 31 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # For readthedocs.org and tox 2 | keyring 3 | ipython 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.1.1 3 | commit = True 4 | tag = True 5 | 6 | [aliases] 7 | test = pytest 8 | 9 | [flake8] 10 | max-line-length = 88 11 | ignore = D413 12 | exclude = 13 | docs 14 | conftest.py 15 | *_test.py 16 | 17 | [bumpversion:file:ipython_secrets.py] 18 | 19 | [bumpversion:file:docs/conf.py] 20 | 21 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36 3 | skipsdist = true 4 | 5 | [testenv] 6 | deps = 7 | pytest 8 | flit 9 | commands = 10 | flit install 11 | py.test --verbose {toxinidir} 12 | 13 | [testenv:docs] 14 | basepython = python 15 | changedir = docs 16 | deps = Sphinx 17 | sphinx_rtd_theme 18 | -rrequirements.txt 19 | commands = 20 | sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 21 | --------------------------------------------------------------------------------