├── .coveragerc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README_P.rst
├── coo.png
├── coo
├── __init__.py
├── _exceptions.py
├── _utils.py
└── coo.py
├── docs
├── Makefile
├── make.bat
└── source
│ ├── changelog.rst
│ ├── conf.py
│ ├── index.rst
│ ├── list_schedule.rst
│ ├── schedule.rst
│ └── twitter_api.rst
├── poetry.lock
├── pyproject.toml
├── requirements.txt
├── setup.py
└── tests
├── __init__.py
├── test_coo.py
└── test_utils.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = coo
4 |
5 | [report]
6 | omit =
7 | tests/*
8 | setup.py
9 | exclude_lines =
10 | def __repr__
11 | def __str__
12 | if settings.DEBUG
13 | raise AssertionError
14 | raise NotImplementedError
15 | if __name__ == .__main__.:
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/vim,osx,linux,macos,python,windows,pycharm,sublimetext,visualstudio,visualstudiocode
3 |
4 | ### Linux ###
5 | *~
6 |
7 | # temporary files which can be created if a process still has a handle open of a deleted file
8 | .fuse_hidden*
9 |
10 | # KDE directory preferences
11 | .directory
12 |
13 | # Linux trash folder which might appear on any partition or disk
14 | .Trash-*
15 |
16 | # .nfs files are created when an open file is removed but is still being accessed
17 | .nfs*
18 |
19 | ### macOS ###
20 | # General
21 | .DS_Store
22 | .AppleDouble
23 | .LSOverride
24 |
25 | # Icon must end with two \r
26 | Icon
27 |
28 | # Thumbnails
29 | ._*
30 |
31 | # Files that might appear in the root of a volume
32 | .DocumentRevisions-V100
33 | .fseventsd
34 | .Spotlight-V100
35 | .TemporaryItems
36 | .Trashes
37 | .VolumeIcon.icns
38 | .com.apple.timemachine.donotpresent
39 |
40 | # Directories potentially created on remote AFP share
41 | .AppleDB
42 | .AppleDesktop
43 | Network Trash Folder
44 | Temporary Items
45 | .apdisk
46 |
47 | ### OSX ###
48 | # General
49 |
50 | # Icon must end with two \r
51 |
52 | # Thumbnails
53 |
54 | # Files that might appear in the root of a volume
55 |
56 | # Directories potentially created on remote AFP share
57 |
58 | ### PyCharm ###
59 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
60 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
61 |
62 | # User-specific stuff
63 | .idea/**/workspace.xml
64 | .idea/**/tasks.xml
65 | .idea/**/usage.statistics.xml
66 | .idea/**/dictionaries
67 | .idea/**/shelf
68 |
69 | # Generated files
70 | .idea/**/contentModel.xml
71 |
72 | # Sensitive or high-churn files
73 | .idea/**/dataSources/
74 | .idea/**/dataSources.ids
75 | .idea/**/dataSources.local.xml
76 | .idea/**/sqlDataSources.xml
77 | .idea/**/dynamic.xml
78 | .idea/**/uiDesigner.xml
79 | .idea/**/dbnavigator.xml
80 |
81 | # Gradle
82 | .idea/**/gradle.xml
83 | .idea/**/libraries
84 |
85 | # Gradle and Maven with auto-import
86 | # When using Gradle or Maven with auto-import, you should exclude module files,
87 | # since they will be recreated, and may cause churn. Uncomment if using
88 | # auto-import.
89 | # .idea/modules.xml
90 | # .idea/*.iml
91 | # .idea/modules
92 |
93 | # CMake
94 | cmake-build-*/
95 |
96 | # Mongo Explorer plugin
97 | .idea/**/mongoSettings.xml
98 |
99 | # File-based project format
100 | *.iws
101 |
102 | # IntelliJ
103 | out/
104 |
105 | # mpeltonen/sbt-idea plugin
106 | .idea_modules/
107 |
108 | # JIRA plugin
109 | atlassian-ide-plugin.xml
110 |
111 | # Cursive Clojure plugin
112 | .idea/replstate.xml
113 |
114 | # Crashlytics plugin (for Android Studio and IntelliJ)
115 | com_crashlytics_export_strings.xml
116 | crashlytics.properties
117 | crashlytics-build.properties
118 | fabric.properties
119 |
120 | # Editor-based Rest Client
121 | .idea/httpRequests
122 |
123 | ### PyCharm Patch ###
124 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
125 |
126 | # *.iml
127 | # modules.xml
128 | # .idea/misc.xml
129 | # *.ipr
130 |
131 | # Sonarlint plugin
132 | .idea/sonarlint
133 |
134 | ### Python ###
135 | # Byte-compiled / optimized / DLL files
136 | __pycache__/
137 | *.py[cod]
138 | *$py.class
139 |
140 | # C extensions
141 | *.so
142 |
143 | # Distribution / packaging
144 | .Python
145 | build/
146 | develop-eggs/
147 | dist/
148 | downloads/
149 | eggs/
150 | .eggs/
151 | lib/
152 | lib64/
153 | parts/
154 | sdist/
155 | var/
156 | wheels/
157 | *.egg-info/
158 | .installed.cfg
159 | *.egg
160 | MANIFEST
161 |
162 | # PyInstaller
163 | # Usually these files are written by a python script from a template
164 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
165 | *.manifest
166 | *.spec
167 |
168 | # Installer logs
169 | pip-log.txt
170 | pip-delete-this-directory.txt
171 |
172 | # Unit test / coverage reports
173 | htmlcov/
174 | .tox/
175 | .coverage
176 | .coverage.*
177 | .cache
178 | nosetests.xml
179 | coverage.xml
180 | *.cover
181 | .hypothesis/
182 | .pytest_cache/
183 |
184 | # Translations
185 | *.mo
186 | *.pot
187 |
188 | # Django stuff:
189 | *.log
190 | local_settings.py
191 | db.sqlite3
192 |
193 | # Flask stuff:
194 | instance/
195 | .webassets-cache
196 |
197 | # Scrapy stuff:
198 | .scrapy
199 |
200 | # Sphinx documentation
201 | docs/_build/
202 |
203 | # PyBuilder
204 | target/
205 |
206 | # Jupyter Notebook
207 | .ipynb_checkpoints
208 |
209 | # IPython
210 | profile_default/
211 | ipython_config.py
212 |
213 | # pyenv
214 | .python-version
215 |
216 | # celery beat schedule file
217 | celerybeat-schedule
218 |
219 | # SageMath parsed files
220 | *.sage.py
221 |
222 | # Environments
223 | .env
224 | .venv
225 | env/
226 | venv/
227 | ENV/
228 | env.bak/
229 | venv.bak/
230 |
231 | # Spyder project settings
232 | .spyderproject
233 | .spyproject
234 |
235 | # Rope project settings
236 | .ropeproject
237 |
238 | # mkdocs documentation
239 | /site
240 |
241 | # mypy
242 | .mypy_cache/
243 | .dmypy.json
244 | dmypy.json
245 |
246 | ### Python Patch ###
247 | .venv/
248 |
249 | ### Python.VirtualEnv Stack ###
250 | # Virtualenv
251 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
252 | [Bb]in
253 | [Ii]nclude
254 | [Ll]ib
255 | [Ll]ib64
256 | [Ll]ocal
257 | [Ss]cripts
258 | pyvenv.cfg
259 | pip-selfcheck.json
260 |
261 | ### SublimeText ###
262 | # Cache files for Sublime Text
263 | *.tmlanguage.cache
264 | *.tmPreferences.cache
265 | *.stTheme.cache
266 |
267 | # Workspace files are user-specific
268 | *.sublime-workspace
269 |
270 | # Project files should be checked into the repository, unless a significant
271 | # proportion of contributors will probably not be using Sublime Text
272 | # *.sublime-project
273 |
274 | # SFTP configuration file
275 | sftp-config.json
276 |
277 | # Package control specific files
278 | Package Control.last-run
279 | Package Control.ca-list
280 | Package Control.ca-bundle
281 | Package Control.system-ca-bundle
282 | Package Control.cache/
283 | Package Control.ca-certs/
284 | Package Control.merged-ca-bundle
285 | Package Control.user-ca-bundle
286 | oscrypto-ca-bundle.crt
287 | bh_unicode_properties.cache
288 |
289 | # Sublime-github package stores a github token in this file
290 | # https://packagecontrol.io/packages/sublime-github
291 | GitHub.sublime-settings
292 |
293 | ### Vim ###
294 | # Swap
295 | [._]*.s[a-v][a-z]
296 | [._]*.sw[a-p]
297 | [._]s[a-rt-v][a-z]
298 | [._]ss[a-gi-z]
299 | [._]sw[a-p]
300 |
301 | # Session
302 | Session.vim
303 |
304 | # Temporary
305 | .netrwhist
306 | # Auto-generated tag files
307 | tags
308 | # Persistent undo
309 | [._]*.un~
310 |
311 | ### VisualStudioCode ###
312 | .vscode/*
313 |
314 |
315 | ### Windows ###
316 | # Windows thumbnail cache files
317 | Thumbs.db
318 | ehthumbs.db
319 | ehthumbs_vista.db
320 |
321 | # Dump file
322 | *.stackdump
323 |
324 | # Folder config file
325 | [Dd]esktop.ini
326 |
327 | # Recycle Bin used on file shares
328 | $RECYCLE.BIN/
329 |
330 | # Windows Installer files
331 | *.cab
332 | *.msi
333 | *.msix
334 | *.msm
335 | *.msp
336 |
337 | # Windows shortcuts
338 | *.lnk
339 |
340 | ### VisualStudio ###
341 | ## Ignore Visual Studio temporary files, build results, and
342 | ## files generated by popular Visual Studio add-ons.
343 | ##
344 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
345 |
346 | # User-specific files
347 | *.suo
348 | *.user
349 | *.userosscache
350 | *.sln.docstates
351 |
352 | # User-specific files (MonoDevelop/Xamarin Studio)
353 | *.userprefs
354 |
355 | # Build results
356 | [Dd]ebug/
357 | [Dd]ebugPublic/
358 | [Rr]elease/
359 | [Rr]eleases/
360 | x64/
361 | x86/
362 | bld/
363 | [Bb]in/
364 | [Oo]bj/
365 | [Ll]og/
366 |
367 | # Visual Studio 2015/2017 cache/options directory
368 | .vs/
369 | # Uncomment if you have tasks that create the project's static files in wwwroot
370 | #wwwroot/
371 |
372 | # Visual Studio 2017 auto generated files
373 | Generated\ Files/
374 |
375 | # MSTest test Results
376 | [Tt]est[Rr]esult*/
377 | [Bb]uild[Ll]og.*
378 |
379 | # NUNIT
380 | *.VisualState.xml
381 | TestResult.xml
382 |
383 | # Build Results of an ATL Project
384 | [Dd]ebugPS/
385 | [Rr]eleasePS/
386 | dlldata.c
387 |
388 | # Benchmark Results
389 | BenchmarkDotNet.Artifacts/
390 |
391 | # .NET Core
392 | project.lock.json
393 | project.fragment.lock.json
394 | artifacts/
395 |
396 | # StyleCop
397 | StyleCopReport.xml
398 |
399 | # Files built by Visual Studio
400 | *_i.c
401 | *_p.c
402 | *_h.h
403 | *.ilk
404 | *.meta
405 | *.obj
406 | *.iobj
407 | *.pch
408 | *.pdb
409 | *.ipdb
410 | *.pgc
411 | *.pgd
412 | *.rsp
413 | *.sbr
414 | *.tlb
415 | *.tli
416 | *.tlh
417 | *.tmp
418 | *.tmp_proj
419 | *.vspscc
420 | *.vssscc
421 | .builds
422 | *.pidb
423 | *.svclog
424 | *.scc
425 |
426 | # Chutzpah Test files
427 | _Chutzpah*
428 |
429 | # Visual C++ cache files
430 | ipch/
431 | *.aps
432 | *.ncb
433 | *.opendb
434 | *.opensdf
435 | *.sdf
436 | *.cachefile
437 | *.VC.db
438 | *.VC.VC.opendb
439 |
440 | # Visual Studio profiler
441 | *.psess
442 | *.vsp
443 | *.vspx
444 | *.sap
445 |
446 | # Visual Studio Trace Files
447 | *.e2e
448 |
449 | # TFS 2012 Local Workspace
450 | $tf/
451 |
452 | # Guidance Automation Toolkit
453 | *.gpState
454 |
455 | # ReSharper is a .NET coding add-in
456 | _ReSharper*/
457 | *.[Rr]e[Ss]harper
458 | *.DotSettings.user
459 |
460 | # JustCode is a .NET coding add-in
461 | .JustCode
462 |
463 | # TeamCity is a build add-in
464 | _TeamCity*
465 |
466 | # DotCover is a Code Coverage Tool
467 | *.dotCover
468 |
469 | # AxoCover is a Code Coverage Tool
470 | .axoCover/*
471 | !.axoCover/settings.json
472 |
473 | # Visual Studio code coverage results
474 | *.coverage
475 | *.coveragexml
476 |
477 | # NCrunch
478 | _NCrunch_*
479 | .*crunch*.local.xml
480 | nCrunchTemp_*
481 |
482 | # MightyMoose
483 | *.mm.*
484 | AutoTest.Net/
485 |
486 | # Web workbench (sass)
487 | .sass-cache/
488 |
489 | # Installshield output folder
490 | [Ee]xpress/
491 |
492 | # DocProject is a documentation generator add-in
493 | DocProject/buildhelp/
494 | DocProject/Help/*.HxT
495 | DocProject/Help/*.HxC
496 | DocProject/Help/*.hhc
497 | DocProject/Help/*.hhk
498 | DocProject/Help/*.hhp
499 | DocProject/Help/Html2
500 | DocProject/Help/html
501 |
502 | # Click-Once directory
503 | publish/
504 |
505 | # Publish Web Output
506 | *.[Pp]ublish.xml
507 | *.azurePubxml
508 | # Note: Comment the next line if you want to checkin your web deploy settings,
509 | # but database connection strings (with potential passwords) will be unencrypted
510 | *.pubxml
511 | *.publishproj
512 |
513 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
514 | # checkin your Azure Web App publish settings, but sensitive information contained
515 | # in these scripts will be unencrypted
516 | PublishScripts/
517 |
518 | # NuGet Packages
519 | *.nupkg
520 | # The packages folder can be ignored because of Package Restore
521 | **/[Pp]ackages/*
522 | # except build/, which is used as an MSBuild target.
523 | !**/[Pp]ackages/build/
524 | # Uncomment if necessary however generally it will be regenerated when needed
525 | #!**/[Pp]ackages/repositories.config
526 | # NuGet v3's project.json files produces more ignorable files
527 | *.nuget.props
528 | *.nuget.targets
529 |
530 | # Microsoft Azure Build Output
531 | csx/
532 | *.build.csdef
533 |
534 | # Microsoft Azure Emulator
535 | ecf/
536 | rcf/
537 |
538 | # Windows Store app package directories and files
539 | AppPackages/
540 | BundleArtifacts/
541 | Package.StoreAssociation.xml
542 | _pkginfo.txt
543 | *.appx
544 |
545 | # Visual Studio cache files
546 | # files ending in .cache can be ignored
547 | *.[Cc]ache
548 | # but keep track of directories ending in .cache
549 | !*.[Cc]ache/
550 |
551 | # Others
552 | ClientBin/
553 | ~$*
554 | *.dbmdl
555 | *.dbproj.schemaview
556 | *.jfm
557 | *.pfx
558 | *.publishsettings
559 | orleans.codegen.cs
560 |
561 | # Including strong name files can present a security risk
562 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
563 | #*.snk
564 |
565 | # Since there are multiple workflows, uncomment next line to ignore bower_components
566 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
567 | #bower_components/
568 |
569 | # RIA/Silverlight projects
570 | Generated_Code/
571 |
572 | # Backup & report files from converting an old project file
573 | # to a newer Visual Studio version. Backup files are not needed,
574 | # because we have git ;-)
575 | _UpgradeReport_Files/
576 | Backup*/
577 | UpgradeLog*.XML
578 | UpgradeLog*.htm
579 | ServiceFabricBackup/
580 | *.rptproj.bak
581 |
582 | # SQL Server files
583 | *.mdf
584 | *.ldf
585 | *.ndf
586 |
587 | # Business Intelligence projects
588 | *.rdl.data
589 | *.bim.layout
590 | *.bim_*.settings
591 | *.rptproj.rsuser
592 |
593 | # Microsoft Fakes
594 | FakesAssemblies/
595 |
596 | # GhostDoc plugin setting file
597 | *.GhostDoc.xml
598 |
599 | # Node.js Tools for Visual Studio
600 | .ntvs_analysis.dat
601 | node_modules/
602 |
603 | # Visual Studio 6 build log
604 | *.plg
605 |
606 | # Visual Studio 6 workspace options file
607 | *.opt
608 |
609 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
610 | *.vbw
611 |
612 | # Visual Studio LightSwitch build output
613 | **/*.HTMLClient/GeneratedArtifacts
614 | **/*.DesktopClient/GeneratedArtifacts
615 | **/*.DesktopClient/ModelManifest.xml
616 | **/*.Server/GeneratedArtifacts
617 | **/*.Server/ModelManifest.xml
618 | _Pvt_Extensions
619 |
620 | # Paket dependency manager
621 | .paket/paket.exe
622 | paket-files/
623 |
624 | # FAKE - F# Make
625 | .fake/
626 |
627 | # JetBrains Rider
628 | .idea/
629 | *.sln.iml
630 |
631 | # CodeRush
632 | .cr/
633 |
634 | # Python Tools for Visual Studio (PTVS)
635 | *.pyc
636 |
637 | # Cake - Uncomment if you are using it
638 | # tools/**
639 | # !tools/packages.config
640 |
641 | # Tabs Studio
642 | *.tss
643 |
644 | # Telerik's JustMock configuration file
645 | *.jmconfig
646 |
647 | # BizTalk build output
648 | *.btp.cs
649 | *.btm.cs
650 | *.odx.cs
651 | *.xsd.cs
652 |
653 | # OpenCover UI analysis results
654 | OpenCover/
655 |
656 | # Azure Stream Analytics local run output
657 | ASALocalRun/
658 |
659 | # MSBuild Binary and Structured Log
660 | *.binlog
661 |
662 | # NVidia Nsight GPU debugger configuration file
663 | *.nvuser
664 |
665 | # MFractors (Xamarin productivity tool) working folder
666 | .mfractor/
667 |
668 | # Local History for Visual Studio
669 | .localhistory/
670 |
671 |
672 | # End of https://www.gitignore.io/api/vim,osx,linux,macos,python,windows,pycharm,sublimetext,visualstudio,visualstudiocode
673 | # Personal development files
674 | .pyre/*
675 | docs/source/_build
676 | import.py
677 | me.jpeg
678 | mac.jpeg
679 | pip-wheel-metadata/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | # - "3.6"
4 | - "3.7-dev"
5 |
6 | # command to install dependencies
7 | install:
8 | - pip install -r requirements.txt
9 | - pip install codecov pytest-cov
10 |
11 | # command to run tests
12 | script:
13 | - pytest
14 | - pytest --cov=./
15 |
16 | after_success:
17 | - codecov
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [Unreleased]
4 |
5 | - Added global template support for `Coo.schedule(template=template)`.
6 | - Added an additional check for the type of the Twitter credentials.
7 | - DEV: removed Type Check from the project.
8 |
9 | ## [0.1.3] - 2018-12-13
10 |
11 | - Added support to post updates randomly on `Coo.tweet(aleatory=True)`.
12 | - Added support for updates with a single media file for all tweets on `Coo.tweet()`.
13 | - Added support for updates with a single media file for all tweets on `Coo.schedule()`.
14 | - Added support for updates with a different media file for each tweet on `Coo.schedule()`.
15 |
16 | ## [0.1.2] - 2018-11-29
17 |
18 | - Fixed template overwriting the updates when `$message` is not provided.
19 | - Fixed lots of typos.
20 |
21 | ## [0.1.1] - 2018-11-21
22 |
23 | - Fixed typos and README_P.rst for PyPI
24 |
25 | ## [0.1.0] - 2018-11-21
26 |
27 | Initial Release
28 |
29 | [Unreleased]: https://github.com/wilfredinni/coo/tree/master
30 | [0.1.3]: https://github.com/wilfredinni/coo/releases/tag/0.1.3
31 | [0.1.2]: https://github.com/wilfredinni/coo/releases/tag/0.1.2
32 | [0.1.1]: https://github.com/wilfredinni/coo/releases/tag/0.1.1
33 | [0.1.0]: https://github.com/wilfredinni/coo/releases/tag/0.1.0
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Carlos Montecinos Geisse
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | coo: schedule Twitter updates with easy
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Coo is an easy to use Python library for scheduling Twitter updates. To use it, you need to first apply for a developer account in the [Twitter Developers Platform](https://developer.twitter.com/) and generate the Keys and Access Tokens.
28 |
29 |
30 | ```shell
31 | pip install coo
32 | ```
33 |
34 | Initializing
35 |
36 | ```python
37 | from coo import Coo
38 |
39 | at = Coo(
40 | "consumer_key",
41 | "consumer_secret",
42 | "access_token",
43 | "access_token_secret",
44 | preview=False
45 | )
46 | ```
47 |
48 | Alternatively, you can set `preview=True` and print your tweets in the terminal instead to post them on Twitter.
49 |
50 | Scheduling Twitter updates:
51 |
52 | ```python
53 | from coo import Coo
54 |
55 | at = Coo(
56 | "consumer_key",
57 | "consumer_secret",
58 | "access_token",
59 | "access_token_secret"
60 | )
61 |
62 | tweets = [
63 | ("2030-10-28 18:50", template, "My Twitter update with a template."),
64 | ("2030-10-29 18:15", template2, "Update with a different template."),
65 | ("2030-11-01 13:45", None, "Awesome Twitter update without a template."),
66 | ]
67 |
68 | at.schedule(tweets, time_zone="America/Santiago")
69 | ```
70 |
71 | Or you can use a list of strings and add a `delay`, `interval` and a `template`:
72 |
73 | ```python
74 | tweets = [
75 | "My first awesome Twitter Update",
76 | "My second awesome Twitter Update",
77 | "My third awesome Twitter Update",
78 | "My fourth awesome Twitter Update",
79 | "My fifth awesome Twitter Update",
80 | "My sixth awesome Twitter Update",
81 | ]
82 |
83 | at.tweet(tweets, delay="13:45", interval="four_hours", template=my_template)
84 | ```
85 |
86 | For more detailed options and usage, keep reading or check the [documentation](https://coo.readthedocs.io/en/latest/) :blue_book:.
87 |
88 |
89 | ## Scheduling Twitter Updates
90 |
91 | Schedule updates with `datetime` strings or integers and use [custom templates](#Templates) if needed.
92 |
93 | ```python
94 | Coo.schedule(updates, time_zone, media)
95 | ```
96 |
97 | Full example:
98 |
99 | ```python
100 | from coo import Coo
101 |
102 | at = Coo(
103 | "consumer_key",
104 | "consumer_secret",
105 | "access_token",
106 | "access_token_secret"
107 | )
108 |
109 | tweets = [
110 | # datetime with and without templates
111 | ("2030-10-28 18:50", template, "My Twitter update with a template."),
112 | ("2030-10-29 18:15", template2, "Update with a different template."),
113 | ("2030-11-01 13:45", None, "Awesome Twitter update without a template."),
114 |
115 | # date with and without templates
116 | ("2030-12-25", template3, "Merry christmas!"),
117 | ("2031-01-01", None, "And a happy new year!"),
118 |
119 | # time with and without templates
120 | ("18:46", template2, "Will be post today at 18:46."),
121 | ("23:00", None, "A tweet for today at 23:00."),
122 |
123 | # integer (seconds) with and without templates
124 | (3600, template, "This tweet will be posted in an hour."),
125 | (86400, None, "This one, tomorrow at the same hour."),
126 | ]
127 |
128 | at.schedule(tweets, time_zone="America/Santiago")
129 | ```
130 |
131 | #### Notes for parsing DateTime strings
132 |
133 | - If a time zone is not specified, it will set to `local`.
134 | - The time will be set to 00:00:00 if it's not specified.
135 | - When passing only time information the date will default to today.
136 | - A future date is needed, otherwise a `ScheduleError` is raised.
137 |
138 | Here you can find all the [Time Zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) available.
139 |
140 | #### Media files
141 |
142 | There are two ways to add media files to your tweets. The first and easiest is to use one global file for all the updates:
143 |
144 | ```python
145 | at.schedule(tweets, time_zone="America/Santiago", media="path/to/file.png")
146 | ```
147 |
148 | Also, an individual file can be set for each one of the updates:
149 |
150 | ```python
151 | tweets = [
152 | ("2030-10-28 18:50", template, "Update with an image.", "pics/owl.png"),
153 | ("2030-10-29 18:15", template, "Update with other media.", "videos/funny_video.mp4"),
154 | ("2030-11-01 13:45", template, "Tweet without media."),
155 | ]
156 | ```
157 |
158 | Finally, it is possible to combine these to ways. For example, if most of the tweets are gonna use the same media and just a few will have a different or none:
159 |
160 | ```python
161 | tweets = [
162 | ("2030-11-01 13:45", template, "Tweet with global media."),
163 | ("2030-11-02 13:45", template, "Tweet with global media."),
164 | ("2030-11-03 13:45", template, "Tweet with global media."),
165 | ("2030-11-04 13:45", template, "Tweet with global media."),
166 | ("2030-11-05 13:45", template, "Tweet with global media."),
167 | ("2030-11-06 13:45", template, "Tweet with global media."),
168 | ("2030-11-07 13:45", template, "Tweet with global media."),
169 | ("2030-11-08 13:45", template, "Tweet without media.", None),
170 | ("2030-11-09 13:45", template, "Tweet without media.", None),
171 | ("2030-12-10 18:50", template, "Update with an image.", "pics/owl.png"),
172 | ("2030-12-11 18:15", template, "Update with other media.", "videos/funny_video.mp4"),
173 | ]
174 |
175 | at.schedule(tweets, time_zone="America/Santiago", media="path/to/global_media.png")
176 | ```
177 |
178 | ## Tweet a list of strings
179 |
180 | Post ordered updates with `delay`, `interval`, and a [template](#Templates) if needed.
181 |
182 | ```python
183 | Coo.tweet(updates, delay, interval, template, media, time_zone, aleatory)
184 | ```
185 |
186 | ```python
187 | from coo import Coo
188 |
189 | at = Coo(
190 | "consumer_key",
191 | "consumer_secret",
192 | "access_token",
193 | "access_token_secret"
194 | )
195 |
196 | tweets = [
197 | "My first awesome Twitter Update",
198 | "My second awesome Twitter Update",
199 | "My third awesome Twitter Update",
200 | "My fourth awesome Twitter Update",
201 | "My fifth awesome Twitter Update",
202 | "My sixth awesome Twitter Update",
203 | ]
204 |
205 | # post the twitter updates
206 | at.tweet(tweets)
207 | ```
208 |
209 | #### Delay
210 |
211 | You can use `datetime`, `date` and `time` strings, integers as seconds and some strings as [keywords](#Delay-and-Interval-Keywords): `half_hour`, `one_hour`, `one_day` and `one_week` between others to delay the post of your first update.
212 |
213 | ```python
214 | # datetime, date and time strings
215 | at.tweet(tweets, delay="2030-11-24 13:45", time_zone="America/Santiago")
216 | at.tweet(tweets, delay="2030-11-24", time_zone="Australia/Sydney")
217 | at.tweet(tweets, delay="13:45", time_zone="America/New_York")
218 |
219 | # "keywords"
220 | at.tweet(tweets, delay="one_week")
221 |
222 | # integer
223 | at.tweet(tweets, delay=604800)
224 | ```
225 |
226 | Remember to read the [Notes for parsing DateTime strings](#Notes-for-parsing-DateTime-strings).
227 |
228 | #### Interval
229 |
230 | Use integers as seconds or some strings as [keywords](#Delay-and-Interval-Keywords): `half_hour`, `one_hour`, `one_day` and `one_week` between others.
231 |
232 | ```python
233 | # "keywords"
234 | at.tweet(tweets, interval="four_hours")
235 |
236 | # integers
237 | at.tweet(tweets, interval=14400)
238 | ```
239 |
240 | #### Template
241 |
242 | And of course, you can also set one [template](#Templates) for each one of the updates.
243 |
244 | ```python
245 | at.tweet(tweets, template=template)
246 | ```
247 |
248 | #### Media files
249 |
250 | Use one media file for all of your updates:
251 |
252 | ```python
253 | at.tweet(tweets, media="path/to/media.jpeg")
254 | ```
255 |
256 | #### Random updates
257 |
258 | To tweet your updates randomly:
259 |
260 | ```python
261 | at.tweet(tweets, aleatory=True)
262 | ```
263 |
264 | #### Delay and Interval Keywords
265 |
266 | | Keyword | Seconds |
267 | | ---------------- | ------- |
268 | | now | 0 |
269 | | half_hour | 1800 |
270 | | one_hour | 3600 |
271 | | two_hours | 7200 |
272 | | four_hours | 14400 |
273 | | six_hours | 21600 |
274 | | eight_hours | 28800 |
275 | | ten_hours | 36000 |
276 | | twelve_hours | 43200 |
277 | | fourteen_hours | 50400 |
278 | | sixteen_hours | 57600 |
279 | | eighteen_hours | 64800 |
280 | | twenty_hours | 72000 |
281 | | twenty_two_hours | 79200 |
282 | | one_day | 86400 |
283 | | two_days | 172800 |
284 | | three_days | 259200 |
285 | | four_days | 345600 |
286 | | five_days | 432000 |
287 | | six_days | 518400 |
288 | | one_week | 604800 |
289 |
290 |
291 | ## Templates
292 |
293 | Templates are very simple, just use a multiline string and add a `$message` where you want your message to appear.
294 |
295 | ```python
296 | template = """My awesome header
297 |
298 | $message
299 |
300 | #python #coding #coo
301 | """
302 | ```
303 |
304 | ## The Twitter API
305 |
306 | Coo is written using the [Python Twitter](https://github.com/bear/python-twitter) wrapper, and through `Coo.api` you gain access to all of his models:
307 |
308 | ```python
309 | # get your followers
310 | followers = at.api.GetFollowers()
311 |
312 | # get your direct messages
313 | d_messages = at.api.GetDirectMessages()
314 |
315 | # favorited tweets
316 | favorites = at.api.GetFavorites()
317 |
318 | # mentions
319 | mentions = at.api.GetMentions()
320 |
321 | # retweets
322 | retweets = at.api.GetRetweets()
323 | ```
324 |
325 | And a lot more. If you are interested, check their [documentation](https://python-twitter.readthedocs.io/en/latest/index.html).
326 |
327 | ## TODO's
328 |
329 | - [x] Add support for random updates.
330 | - [x] Add support for media files.
331 | - [x] Add support for multiple media files.
332 | - [ ] Add support for a history of tweets.
333 | - [ ] Add support for media files from URLs.
334 | - [ ] Add support for one template for all updates on `Coo.schedule`.
335 | - [ ] Support `.toml` files for configuration and tweets.
336 | - [ ] Support resume after a process restart (see [apscheduler](https://github.com/agronholm/apscheduler)).
337 | - [ ] Add a CLI.
338 |
339 | ## Documentation
340 |
341 | Documentation available at [coo.readthedocs.io](https://coo.readthedocs.io/en/latest/).
342 |
--------------------------------------------------------------------------------
/README_P.rst:
--------------------------------------------------------------------------------
1 | =============================
2 | coo: Schedule Twitter updates
3 | =============================
4 |
5 | .. image:: https://badge.fury.io/py/coo.svg
6 | :target: https://badge.fury.io/py/coo
7 | .. image:: https://travis-ci.org/wilfredinni/coo.svg?branch=master
8 | :target: https://travis-ci.org/wilfredinni/coo
9 | .. image:: https://codecov.io/gh/wilfredinni/coo/branch/master/graph/badge.svg
10 | :target: https://codecov.io/gh/wilfredinni/coo
11 | .. image:: https://readthedocs.org/projects/coo/badge/?version=latest
12 | :target: https://coo.readthedocs.io/en/latest/?badge=latest
13 | .. image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg
14 | :target: https://opensource.org/licenses/Apache-2.0
15 |
16 | Coo is an easy to use Python library for scheduling Twitter updates. To use it, you need
17 | to first apply for a developer account in the
18 | `Twitter Developers Platform `_ and generate the Keys and
19 | Access Tokens.
20 |
21 | .. code-block:: python
22 |
23 | pip install coo
24 |
25 | Initializing
26 |
27 | .. code-block:: python
28 |
29 | from coo import Coo
30 |
31 | at = Coo(
32 | "consumer_key",
33 | "consumer_secret",
34 | "access_token",
35 | "access_token_secret",
36 | preview=False,
37 | )
38 |
39 | Alternatively, you can set ``preview=True`` and print your tweets in the terminal instead
40 | to post them on Twitter.
41 |
42 | Scheduling Twitter updates:
43 |
44 |
45 | .. code-block:: python
46 |
47 | from coo import Coo
48 |
49 | at = Coo(
50 | "consumer_key",
51 | "consumer_secret",
52 | "access_token",
53 | "access_token_secret"
54 | )
55 |
56 | tweets = [
57 | ("2030-12-05 16:30", template, "Awesome Twitter update."),
58 | ("2030-10-28 18:50", template, "Another awesome Twitter update."),
59 | ("2030-10-29 18:15", template2, "One more update."),
60 | ("2030-11-01 13:45", None, "Twitter update without a template."),
61 |
62 | at.schedule(tweets, time_zone="America/Santiago")
63 |
64 | Or you can use a list of strings and add a ``delay``, ``interval`` and a ``template``:
65 |
66 | .. code-block:: python
67 |
68 | tweets = [
69 | "My first awesome Twitter Update",
70 | "My second awesome Twitter Update",
71 | "My third awesome Twitter Update",
72 | "My fourth awesome Twitter Update",
73 | "My fifth awesome Twitter Update",
74 | "My sixth awesome Twitter Update",
75 | ]
76 |
77 | at.tweet(tweets, delay="13:45", interval="four_hours", template=my_template)
78 |
79 | Schedule Twitter Updates
80 | ========================
81 |
82 | Schedule updates with `datetime` strings or integers and use custom a `Template`_ if needed.
83 |
84 | .. code-block:: python
85 |
86 | Coo.schedule(updates, time_zone)
87 |
88 | Full example:
89 |
90 | .. code-block:: python
91 |
92 | from coo import Coo
93 |
94 | at = Coo(
95 | "consumer_key",
96 | "consumer_secret",
97 | "access_token",
98 | "access_token_secret"
99 | )
100 |
101 | tweets = [
102 | # datetime with and without templates
103 | ("2030-10-28 18:50", template, "My Twitter update with a template."),
104 | ("2030-10-29 18:15", template2, "Update with a different template."),
105 | ("2030-11-01 13:45", None, "Twitter update without a template."),
106 |
107 | # date with and without templates
108 | ("2030-12-25", template3, "Merry christmas!"),
109 | ("2031-01-01", None, "And a happy new year!"),
110 |
111 | # time with and without templates
112 | ("18:46", template2, "Will be post today at 18:46."),
113 | ("23:00", None, "A tweet for today at 23:00."),
114 |
115 | # integer (seconds) with and without templates
116 | (3600, template, "This tweet will be posted in an hour."),
117 | (86400, None, "This one, tomorrow at the same hour."),
118 | ]
119 |
120 | at.schedule(tweets, time_zone="America/Santiago")
121 |
122 | Parsing DateTime strings
123 | ^^^^^^^^^^^^^^^^^^^^^^^^
124 |
125 | - If a time zone is not specified, it will set to `local`.
126 | - The time will be set to 00:00:00 if it's not specified.
127 | - When passing only time information the date will default to today.
128 | - A future date is needed, otherwise a `ScheduleError` is raised.
129 |
130 | Here you can find all the
131 | `Time Zones `_.
132 |
133 | Media Files
134 | ^^^^^^^^^^^
135 |
136 | There are two ways to add media files to your tweets. The first and easiest is to use one global file for all the updates:
137 |
138 | .. code-block:: python
139 |
140 | at.schedule(tweets, time_zone="America/Santiago", media="path/to/file.png")
141 |
142 | Also, an individual file can be set for each one of the updates:
143 |
144 | .. code-block:: python
145 |
146 | tweets = [
147 | ("2030-10-28 18:50", template, "Update with an image.", "pics/owl.png"),
148 | ("2030-10-29 18:15", template, "Update with other media.", "videos/funny_video.mp4"),
149 | ("2030-11-01 13:45", template, "Tweet without media."),
150 | ]
151 |
152 | Finally, it is possible to combine these to ways. For example, if most of the tweets are gonna use the same media and just a few will have a different or none:
153 |
154 | .. code-block:: python
155 |
156 | tweets = [
157 | ("2030-11-01 13:45", template, "Tweet with global media."),
158 | ("2030-11-02 13:45", template, "Tweet with global media."),
159 | ("2030-11-03 13:45", template, "Tweet with global media."),
160 | ("2030-11-04 13:45", template, "Tweet with global media."),
161 | ("2030-11-05 13:45", template, "Tweet with global media."),
162 | ("2030-11-06 13:45", template, "Tweet with global media."),
163 | ("2030-11-07 13:45", template, "Tweet with global media."),
164 | ("2030-11-08 13:45", template, "Tweet without media.", None),
165 | ("2030-11-09 13:45", template, "Tweet without media.", None),
166 | ("2030-12-10 18:50", template, "Update with an image.", "pics/owl.png"),
167 | ("2030-12-11 18:15", template, "Update with other media.", "videos/funny_video.mp4"),
168 | ]
169 |
170 | at.schedule(tweets, time_zone="America/Santiago", media="path/to/global_media.png")
171 |
172 |
173 | Tweet an ordered list of strings
174 | ================================
175 |
176 | Post ordered updates with `Delay`_, `Interval`_, and a `Template`_ if needed.
177 |
178 | .. code-block:: python
179 |
180 | Coo.tweet(updates, delay, interval, template, time_zone)
181 |
182 | .. code-block:: python
183 |
184 | from coo import Coo
185 |
186 | at = Coo(
187 | "consumer_key",
188 | "consumer_secret",
189 | "access_token",
190 | "access_token_secret"
191 | )
192 |
193 | tweets = [
194 | "My first awesome Twitter Update",
195 | "My second awesome Twitter Update",
196 | "My third awesome Twitter Update",
197 | "My fourth awesome Twitter Update",
198 | "My fifth awesome Twitter Update",
199 | "My sixth awesome Twitter Update",
200 | ]
201 |
202 | # post the twitter updates
203 | at.tweet(tweets)
204 |
205 | Delay
206 | ^^^^^
207 |
208 | You can use ``datetime``, ``date`` and ``time`` strings, integers as seconds and some
209 | `Keywords`_: ``half_hour``, ``one_hour``, ``one_day`` and ``one_week`` between others to
210 | delay the post of your first update.
211 |
212 | .. code-block:: python
213 |
214 | # datetime, date and time strings
215 | at.tweet(tweets, delay="2030-11-24 13:45", time_zone="America/Santiago")
216 | at.tweet(tweets, delay="2030-11-24", time_zone="Australia/Sydney")
217 | at.tweet(tweets, delay="13:45", time_zone="America/New_York")
218 |
219 | # "keywords"
220 | at.tweet(tweets, delay="one_week")
221 |
222 | # integer
223 | at.tweet(tweets, delay=604800)
224 |
225 | When parsing DateTime strings:
226 |
227 | - If a time zone is not specified, it will set to `local`.
228 | - The time will be set to 00:00:00 if it's not specified.
229 | - When passing only time information the date will default to today.
230 | - A future date is needed, otherwise a `ScheduleError` is raised.
231 |
232 | Here you can find all the `Time Zones `_.
233 |
234 | Interval
235 | ^^^^^^^^
236 |
237 | Use integers as seconds or some strings as `Keywords`_: ``half_hour``, ``one_hour``,
238 | ``one_day`` and ``one_week`` between others.
239 |
240 | .. code-block:: python
241 |
242 | # "keywords"
243 | at.tweet(tweets, interval="four_hours")
244 |
245 | # integers
246 | at.tweet(tweets, interval=14400)
247 |
248 | Media files
249 | ^^^^^^^^^^^
250 |
251 | Use one media file for all of your updates:
252 |
253 | .. code-block:: python
254 |
255 | at.tweet(tweets, media="path/to/media.jpeg")
256 |
257 | Random updates
258 | ^^^^^^^^^^^^^^
259 |
260 | To tweet your updates randomly:
261 |
262 | .. code-block:: python
263 |
264 | at.tweet(tweets, aleatory=True)
265 |
266 | Keywords
267 | ^^^^^^^^
268 |
269 | ================ =======
270 | Keyword Seconds
271 | ================ =======
272 | now 0
273 | half_hour 1800
274 | one_hour 3600
275 | two_hours 7200
276 | four_hours 14400
277 | six_hours 21600
278 | eight_hours 28800
279 | ten_hours 36000
280 | twelve_hours 43200
281 | fourteen_hours 50400
282 | sixteen_hours 57600
283 | eighteen_hours 64800
284 | twenty_hours 72000
285 | twenty_two_hours 79200
286 | one_day 86400
287 | two_days 172800
288 | three_days 259200
289 | four_days 345600
290 | five_days 432000
291 | six_days 518400
292 | one_week 604800
293 | ================ =======
294 |
295 | Template
296 | ========
297 |
298 | Templates are very simple, just use a multiline string and add a ``$message``
299 | where you want your message to appear.
300 |
301 | .. code-block:: python
302 |
303 | template = """My awesome header
304 |
305 | $message
306 |
307 | #python #coding #coo
308 | """
309 |
310 | The Twitter API
311 | ===============
312 |
313 | Coo is written using the `Python Twitter `_
314 | wrapper, and through `Coo.api` you gain access to all of his models:
315 |
316 | .. code-block:: python
317 |
318 | # get your followers
319 | followers = at.api.GetFollowers()
320 |
321 | # get your direct messages
322 | d_messages = at.api.GetDirectMessages()
323 |
324 | # favorited tweets
325 | favorites = at.api.GetFavorites()
326 |
327 | # mentions
328 | mentions = at.api.GetMentions()
329 |
330 | # retweets
331 | retweets = at.api.GetRetweets()
332 |
333 | And a lot more. If you are interested, check their `documentation `_.
334 |
335 | Documentation
336 | =============
337 |
338 | Documentation available at `readthedocs.org `_.
--------------------------------------------------------------------------------
/coo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wilfredinni/coo/e6dbfc11aadfdde27c6edbd0343adb54b849a5fc/coo.png
--------------------------------------------------------------------------------
/coo/__init__.py:
--------------------------------------------------------------------------------
1 | from .coo import Coo
2 |
3 |
4 | __title__ = "coo"
5 | __license__ = "Apache 2.0"
6 | __version__ = "0.1.3"
7 | __author__ = "Carlos Montecinos Geisse"
8 | __contact__ = "carlos.w.montecinos@gmail.com"
9 | __url__ = "https://github.com/wilfredinni/coo"
10 |
--------------------------------------------------------------------------------
/coo/_exceptions.py:
--------------------------------------------------------------------------------
1 | class TweetTypeError(TypeError):
2 | """
3 | Raised when the argument provided to 'msg' is not a list of strings.
4 | """
5 |
6 | wrongListMsg = "A List of Strings is required."
7 |
8 |
9 | class TemplateError(TypeError):
10 | """ Raised when the wrong data type is provided in a template. """
11 |
12 | templateInfoMsg = "template must be a string."
13 | templateMsgErr = "The template must contain a '$message'."
14 |
15 |
16 | class ScheduleError(TypeError):
17 |
18 | wrongListMsg = "A list of tuples is required."
19 | tupleLenError = "Every tuple need a length of 3 or 4."
20 | pastDateError = "A future date is needed."
21 |
--------------------------------------------------------------------------------
/coo/_utils.py:
--------------------------------------------------------------------------------
1 | from string import Template
2 | import time
3 |
4 | import pendulum
5 | from pendulum.parsing.exceptions import ParserError
6 |
7 | from ._exceptions import TemplateError, ScheduleError
8 |
9 |
10 | TIME_DICT = {
11 | "now": 0,
12 | "half_hour": 1800,
13 | "one_hour": 3600,
14 | "two_hours": 7200,
15 | "four_hours": 14400,
16 | "six_hours": 21600,
17 | "eight_hours": 28800,
18 | "ten_hours": 36000,
19 | "twelve_hours": 43200,
20 | "fourteen_hours": 50400,
21 | "sixteen_hours": 57600,
22 | "eighteen_hours": 64800,
23 | "twenty_hours": 72000,
24 | "twenty_two_hours": 79200,
25 | "one_day": 86400,
26 | "two_days": 172800,
27 | "three_days": 259200,
28 | "four_days": 345600,
29 | "five_days": 432000,
30 | "six_days": 518400,
31 | "one_week": 604800,
32 | }
33 |
34 |
35 | def parse_time(date_time, time_zone):
36 | """Returns the seconds between now and the scheduled time."""
37 | now = pendulum.now(time_zone)
38 | update = pendulum.parse(date_time, tz=time_zone)
39 |
40 | # If a time zone is not specified, it will be set to local.
41 | # When passing only time information the date will default to today.
42 | # The time will be set to 00:00:00 if it's not specified.
43 | # A future date is needed.
44 |
45 | secs = update - now
46 | if secs.seconds < 0:
47 | raise ScheduleError(ScheduleError.pastDateError)
48 |
49 | return secs.seconds
50 |
51 |
52 | def parse_or_get(schedule_time, time_zone):
53 | """Returns seconds from dictionaries, integers or a DateTime."""
54 | if isinstance(schedule_time, int):
55 | return schedule_time
56 | elif schedule_time in TIME_DICT:
57 | return TIME_DICT.get(schedule_time)
58 |
59 | try:
60 | return parse_time(schedule_time, time_zone)
61 | except ParserError:
62 | raise TypeError("An integer, valid datetime or keyword is needed.")
63 |
64 |
65 | def zzz(sleep_time, time_zone=None):
66 | """Delay sleep and interval time sleep. """
67 | try:
68 | time.sleep(sleep_time)
69 | except TypeError:
70 | sleep_time = parse_or_get(sleep_time, time_zone)
71 | time.sleep(sleep_time)
72 |
73 |
74 | def tweet_template(update, template):
75 | """Returns the the update in the template."""
76 | # Raise an Error if the template does not contain a $message.
77 | if template and "$message" not in template:
78 | raise TemplateError(TemplateError.templateMsgErr)
79 |
80 | try:
81 | return Template(template).substitute(message=update)
82 | except TypeError:
83 | raise TemplateError(TemplateError.templateInfoMsg)
84 |
--------------------------------------------------------------------------------
/coo/coo.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import random
3 | import asyncio
4 |
5 | import twitter
6 |
7 | from ._utils import zzz, tweet_template, parse_or_get
8 | from ._exceptions import ScheduleError, TweetTypeError
9 |
10 |
11 | class Coo:
12 | """
13 | Schedule Twitter Updates with Easy.
14 |
15 | Note: to use this library you need to create an account on
16 | https://developer.twitter.com/ and generate Keys and Access
17 | Tokens.
18 |
19 | Attributes
20 | ----------
21 | consumer : str
22 | Twitter consumer key.
23 | consumer_secret : str
24 | Twitter consumer secret.
25 | token : str
26 | Twitter token.
27 | token_secret : str
28 | Twitter token secret.
29 | preview : bool, optional
30 | Print the update(s) on the console (default is 'False).
31 |
32 | Methods
33 | -------
34 | verify()
35 | Verify if the authentication is valid.
36 | tweet(updates, _delay=None, _interval=None, template=None, time_zone="local")
37 | Post Twitter Updates from a list of strings.
38 | schedule(updates, time_zone='local')
39 | Post multiple Twitter Updates from a list of tuples.
40 | _str_update()
41 | Post a Twitter Update from a string.
42 | """
43 |
44 | time_zone: str = "local"
45 | media = None
46 | global_media = None
47 | global_template = None
48 |
49 | def __init__(self, consumer, consumer_secret, token, token_secret, preview=False):
50 | """
51 | Parameters
52 | ----------
53 | consumer : str
54 | Twitter consumer key.
55 | consumer_secret : str
56 | Twitter consumer secret.
57 | token : str
58 | Twitter token.
59 | token_secret : str
60 | Twitter token secret.
61 | preview : bool, optional
62 | Print the update(s) on the console.
63 | """
64 | # check for correct credentials types
65 | self._check_credentials_type(consumer, consumer_secret, token, token_secret)
66 |
67 | # https://github.com/bear/python-twitter
68 | self.consumer = consumer
69 | self.consumer_secret = consumer_secret
70 | self.token = token
71 | self.token_secret = token_secret
72 |
73 | # True to preview the update in the console.
74 | self.preview = preview
75 |
76 | # _interval and _delay switches.
77 | self._delay_time = True
78 | self._interval_time = False
79 |
80 | # The async loop for the custom updates.
81 | self.loop = asyncio.get_event_loop()
82 |
83 | def _check_credentials_type(self, *args):
84 | for credential in args:
85 | if not isinstance(credential, str):
86 | raise TypeError("Twitter credentials must be strings")
87 |
88 | @property
89 | def api(self):
90 | """
91 | Through Coo.api you gain access to all of the Python Twitter
92 | wrapper models:
93 |
94 | from coo import Coo
95 |
96 | >>> at = Coo("consumer", "consumer_secret", "access_token", "token_secret")
97 | >>> at.api.GetFollowers()
98 |
99 | More info: https://python-twitter.readthedocs.io/en/latest/index.html
100 | """
101 | return twitter.Api(
102 | self.consumer, self.consumer_secret, self.token, self.token_secret
103 | )
104 |
105 | @property
106 | def verify(self):
107 | """Verify if the authentication is valid."""
108 | return self.api.VerifyCredentials()
109 |
110 | @classmethod
111 | def set_time_zone(cls, time_zone):
112 | cls.time_zone = time_zone
113 |
114 | @classmethod
115 | def set_media_file(cls, media):
116 | cls.media = media
117 |
118 | @classmethod
119 | def set_global_media_file(cls, global_media):
120 | cls.global_media = global_media
121 |
122 | @classmethod
123 | def set_global_template(cls, global_template):
124 | cls.global_template = global_template
125 |
126 | def tweet(
127 | self,
128 | updates,
129 | delay=None,
130 | interval=None,
131 | template=None,
132 | media=media,
133 | time_zone=time_zone,
134 | aleatory=False,
135 | ):
136 | """
137 | Post Twitter Updates from a list of strings.
138 |
139 | Parameters
140 | ----------
141 | updates : list
142 | A list of strings, each one is a Twitter Update.
143 | _delay : str, int, optional
144 | The time before the first Update.
145 | _interval : str, int, optional
146 | The time between Updates.
147 | template : str, optional
148 | A string to serve as a template. Need to has a "$message".
149 | media : str, optional
150 | PATH to a local file, or a file-like object (something
151 | with a read() method).
152 | time_zone : str, optional
153 | Sets a time zone for parsing datetime strings (default is 'local'):
154 | https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
155 | aleatory : bool, optional
156 | Tweet the updates randomly (default is 'False').
157 |
158 | Raises
159 | ------
160 | TweetTypeError
161 | When "updates" is not a list or its elements are not strings.
162 | """
163 | if not isinstance(updates, list) or not isinstance(updates[0], str):
164 | raise TweetTypeError(TweetTypeError.wrongListMsg)
165 |
166 | if aleatory:
167 | random.shuffle(updates)
168 | if time_zone is not self.time_zone:
169 | self.set_time_zone(time_zone)
170 | if media:
171 | self.set_media_file(Path(media))
172 |
173 | self._delay(delay)
174 | for update in updates:
175 | self._interval(interval)
176 | self._str_update(update, template)
177 |
178 | return updates
179 |
180 | def schedule(
181 | self, updates, time_zone=time_zone, media=media, template=global_template
182 | ):
183 | """
184 | Post multiple Twitter Updates from a list of tuples.
185 |
186 | Parameters
187 | ----------
188 | updates : list
189 | A list of tuples that contains:
190 |
191 | [("datetime", "template", "update msg")]
192 |
193 | e.g.
194 |
195 | [("2040-10-30 00:05", template, "Update msg")]
196 |
197 | Notes for parsing date and time strings:
198 | - If a time zone is not specified, it will be set to local.
199 | - When parsing only time information the date will default to today.
200 | - The time will be set to 00:00:00 if it's not specified.
201 | - A future date is needed, otherwise, a ScheduleError is raised.
202 |
203 | The template is string with a "$message".
204 |
205 | time_zone : str, optional
206 | Sets a time zone for parsing datetime strings (default is 'local'):
207 | https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
208 | media : str, optional
209 | PATH to a local file, or a file-like object (something
210 | with a read() method).
211 | template : str, optional
212 | A global template for all the tweets with a "None" value on "msg[1]".
213 |
214 | Raises
215 | ------
216 | ScheduleError
217 | When the length of a tuple updates is less or greater than 3.
218 | """
219 | if not isinstance(updates[0], tuple):
220 | raise ScheduleError(ScheduleError.wrongListMsg)
221 | if template:
222 | self.set_global_template(template)
223 | if time_zone is not self.time_zone:
224 | self.set_time_zone(time_zone)
225 | if media:
226 | self.set_global_media_file(Path(media))
227 |
228 | self.loop.run_until_complete(self._async_tasks(updates))
229 | self.loop.close()
230 |
231 | def _str_update(self, update, template):
232 | """
233 | Post a Twitter Update from a string.
234 |
235 | Parameters
236 | ----------
237 | update : str
238 | A string representing a Twitter Update.
239 | template : str, optional
240 | A string to serve as a template. Need to has a "$message".
241 |
242 | Returns
243 | -------
244 | twitter.Api.PostUpdate
245 | Post the update to Twitter.
246 | """
247 | if template:
248 | update = tweet_template(update=update, template=template)
249 | elif self.global_template:
250 | update = tweet_template(update=update, template=self.global_template)
251 |
252 | if self.preview:
253 | print(update)
254 | return
255 |
256 | try:
257 | # Try to post with a media file.
258 | with open(self.media, "rb") as media_file: # type: ignore
259 | return self.api.PostUpdate(update, media=media_file)
260 | except TypeError:
261 | # If media is not a readable type, just post the update.
262 | return self.api.PostUpdate(update)
263 |
264 | async def _async_tasks(self, custom_msgs):
265 | """Prepare the asyncio tasks for the custom tweets."""
266 | for msg in set(custom_msgs):
267 | if len(msg) < 3 or len(msg) > 4:
268 | raise ScheduleError(ScheduleError.tupleLenError)
269 |
270 | await asyncio.wait(
271 | [self.loop.create_task(self._custom_updates(post)) for post in custom_msgs]
272 | )
273 |
274 | async def _custom_updates(self, msg):
275 | """
276 | Process custom updates: templates and updates time for every
277 | Twitter update.
278 | """
279 | seconds = parse_or_get(msg[0], self.time_zone)
280 | await asyncio.sleep(seconds)
281 |
282 | if len(msg) == 4 and msg[3] is not None:
283 | self.set_media_file(Path(msg[3]))
284 | elif len(msg) == 3 and self.global_media:
285 | self.set_media_file(Path(self.global_media))
286 | else:
287 | self.set_media_file(None)
288 |
289 | return self._str_update(update=msg[2], template=msg[1])
290 |
291 | def _delay(self, _delay):
292 | """_delay the Post of one or multiple tweets."""
293 | if _delay and self._delay_time:
294 | zzz(_delay, self.time_zone)
295 |
296 | # Set to False to avoid repetition
297 | self._delay_time = False
298 |
299 | def _interval(self, _interval):
300 | """Add an _interval between Twitter Updates."""
301 | # Avoid the first iteration
302 | if _interval and self._interval_time is True:
303 | zzz(_interval)
304 |
305 | # Allow from the second one
306 | if self._interval_time is False:
307 | self._interval_time = True
308 |
309 | def __str__(self):
310 | return f"Twitter User: {self.verify.name}."
311 |
--------------------------------------------------------------------------------
/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 | SOURCEDIR = source
8 | BUILDDIR = build
9 |
10 | # Put it first so that "make" without argument is like "make help".
11 | help:
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13 |
14 | .PHONY: help Makefile
15 |
16 | # Catch-all target: route all unknown targets to Sphinx using the new
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 | %: Makefile
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/source/changelog.rst:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | `0.1.3 `_ - 2018-12-13
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 |
7 | **Added**
8 |
9 | - Added support to post updates randomly on ``Coo.tweet(aleartory=True)``.
10 | - Added support for updates with a single media file for all tweets on ``Coo.tweet()``.
11 | - Added support for updates with a single media file for all tweets on ``Coo.schedule()``.
12 | - Added support for updates with a different media file for each tweet on ``Coo.schedule()``.
13 |
14 | `0.1.2 `_ - 2018-11-29
15 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16 |
17 | **Added**
18 |
19 | - Added changes to CHANGELOG.md.
20 | - Added changes to the documentation.
21 | - Added releases.
22 |
23 | **Fixed**
24 |
25 | - Fixed template overwriting the tweets when `$message` is not provided.
26 | - Fixed lots of typos.
27 |
28 | `0.1.1 `_ - 2018-11-21
29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 |
31 | **Fixed**
32 |
33 | - Fixed typos and README_P.rst for PyPI.
34 |
35 | `0.1.0 `_ - 2018-11-21
36 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37 |
38 | Initial Release
39 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Configuration file for the Sphinx documentation builder.
4 | #
5 | # This file does only contain a selection of the most common options. For a
6 | # full list see the documentation:
7 | # http://www.sphinx-doc.org/en/master/config
8 |
9 | # -- Path setup --------------------------------------------------------------
10 |
11 | # If extensions (or modules to document with autodoc) are in another directory,
12 | # add these directories to sys.path here. If the directory is relative to the
13 | # documentation root, use os.path.abspath to make it absolute, like shown here.
14 | #
15 | # import os
16 | # import sys
17 |
18 | # sys.path.insert(0, os.path.abspath("../coo"))
19 |
20 |
21 | # -- Project information -----------------------------------------------------
22 |
23 | project = "coo"
24 | copyright = "2018, wilfredinni"
25 | author = "wilfredinni"
26 |
27 | # The short X.Y version
28 | version = ""
29 | # The full version, including alpha/beta/rc tags
30 | release = "0.1.0"
31 |
32 |
33 | # -- General configuration ---------------------------------------------------
34 |
35 | # If your documentation needs a minimal Sphinx version, state it here.
36 | #
37 | # needs_sphinx = '1.0'
38 |
39 | # Add any Sphinx extension module names here, as strings. They can be
40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
41 | # ones.
42 | extensions = ["sphinx.ext.autodoc"]
43 |
44 | # Add any paths that contain templates here, relative to this directory.
45 | templates_path = ["ntemplates"]
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 | # The language for content autogenerated by Sphinx. Refer to documentation
57 | # for a list of supported languages.
58 | #
59 | # This is also used if you do content translation via gettext catalogs.
60 | # Usually you set "language" from the command line for these cases.
61 | language = None
62 |
63 | # List of patterns, relative to source directory, that match files and
64 | # directories to ignore when looking for source files.
65 | # This pattern also affects html_static_path and html_extra_path.
66 | exclude_patterns = []
67 |
68 | # The name of the Pygments (syntax highlighting) style to use.
69 | pygments_style = None
70 |
71 |
72 | # -- Options for HTML output -------------------------------------------------
73 |
74 | # The theme to use for HTML and HTML Help pages. See the documentation for
75 | # a list of builtin themes.
76 | #
77 | html_theme = "alabaster"
78 |
79 | # Theme options are theme-specific and customize the look and feel of a theme
80 | # further. For a list of options available for each theme, see the
81 | # documentation.
82 | #
83 | # html_theme_options = {}
84 |
85 | # Add any paths that contain custom static files (such as style sheets) here,
86 | # relative to this directory. They are copied after the builtin static files,
87 | # so a file named "default.css" will overwrite the builtin "default.css".
88 | html_static_path = ["nstatic"]
89 |
90 | # Custom sidebar templates, must be a dictionary that maps document names
91 | # to template names.
92 | #
93 | # The default sidebars (for documents that don't match any pattern) are
94 | # defined by theme itself. Builtin themes are using these templates by
95 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
96 | # 'searchbox.html']``.
97 | #
98 | # html_sidebars = {}
99 |
100 |
101 | # -- Options for HTMLHelp output ---------------------------------------------
102 |
103 | # Output file base name for HTML help builder.
104 | htmlhelp_basename = "coodoc"
105 |
106 |
107 | # -- Options for LaTeX output ------------------------------------------------
108 |
109 | latex_elements = {
110 | # The paper size ('letterpaper' or 'a4paper').
111 | #
112 | # 'papersize': 'letterpaper',
113 | # The font size ('10pt', '11pt' or '12pt').
114 | #
115 | # 'pointsize': '10pt',
116 | # Additional stuff for the LaTeX preamble.
117 | #
118 | # 'preamble': '',
119 | # Latex figure (float) alignment
120 | #
121 | # 'figure_align': 'htbp',
122 | }
123 |
124 | # Grouping the document tree into LaTeX files. List of tuples
125 | # (source start file, target name, title,
126 | # author, documentclass [howto, manual, or own class]).
127 | latex_documents = [
128 | (master_doc, "coo.tex", "coo Documentation", "wilfredinni", "manual")
129 | ]
130 |
131 |
132 | # -- Options for manual page output ------------------------------------------
133 |
134 | # One entry per manual page. List of tuples
135 | # (source start file, name, description, authors, manual section).
136 | man_pages = [(master_doc, "coo", "coo Documentation", [author], 1)]
137 |
138 |
139 | # -- Options for Texinfo output ----------------------------------------------
140 |
141 | # Grouping the document tree into Texinfo files. List of tuples
142 | # (source start file, target name, title, author,
143 | # dir menu entry, description, category)
144 | texinfo_documents = [
145 | (
146 | master_doc,
147 | "coo",
148 | "coo Documentation",
149 | author,
150 | "coo",
151 | "One line description of project.",
152 | "Miscellaneous",
153 | )
154 | ]
155 |
156 |
157 | # -- Options for Epub output -------------------------------------------------
158 |
159 | # Bibliographic Dublin Core info.
160 | epub_title = project
161 |
162 | # The unique identifier of the text. This can be a ISBN number
163 | # or the project homepage.
164 | #
165 | # epub_identifier = ''
166 |
167 | # A unique identification for the text.
168 | #
169 | # epub_uid = ''
170 |
171 | # A list of files that should not be packed into the epub file.
172 | epub_exclude_files = ["search.html"]
173 |
174 |
175 | # -- Extension configuration -------------------------------------------------
176 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | =============================
2 | coo: Schedule Twitter updates
3 | =============================
4 |
5 | .. raw:: html
6 |
7 |
10 |
11 | .. raw:: html
12 |
13 |
29 |
30 | Coo is an easy to use Python library for scheduling Twitter updates. To use it, you need
31 | to first apply for a developer account in the
32 | `Twitter Developers Platform `_ and generate the Keys and
33 | Access Tokens.
34 |
35 | .. code-block:: python
36 |
37 | from coo import Coo
38 |
39 | at = Coo(
40 | "consumer_key",
41 | "consumer_secret",
42 | "access_token",
43 | "access_token_secret"
44 | )
45 |
46 | tweets = [
47 | ("2030-12-05 16:30", template, "Awesome Twitter update."),
48 | ("2030-10-28 18:50", template, "Another awesome Twitter update."),
49 | ("2030-10-29 18:15", template2, "One more update."),
50 | ("2030-11-01 13:45", None, "Twitter update without a template."),
51 |
52 | at.schedule(tweets, time_zone="America/Santiago")
53 |
54 | Or you can use a list of strings and add a ``delay``, ``interval`` and ``template``:
55 |
56 | .. code-block:: python
57 |
58 | tweets = [
59 | "My first awesome Twitter Update",
60 | "My second awesome Twitter Update",
61 | "My third awesome Twitter Update",
62 | "My fourth awesome Twitter Update",
63 | "My fifth awesome Twitter Update",
64 | "My sixth awesome Twitter Update",
65 | ]
66 |
67 | at.tweet(tweets, delay="13:45", interval="four_hours", template=my_template)
68 |
69 | User Guide
70 | ^^^^^^^^^^
71 |
72 | .. toctree::
73 | :maxdepth: 2
74 |
75 | schedule
76 | list_schedule
77 | twitter_api
78 | changelog
79 |
80 | Indices and tables
81 | ==================
82 |
83 | * :ref:`genindex`
84 | * :ref:`modindex`
85 | * :ref:`search`
86 |
--------------------------------------------------------------------------------
/docs/source/list_schedule.rst:
--------------------------------------------------------------------------------
1 | Schedule a list of strings
2 | ==========================
3 |
4 | Post ordered updates with `Delay`_, `Interval`_, and a `Template`_ if needed.
5 |
6 | .. code-block:: python
7 |
8 | Coo.tweet(updates, delay, interval, template, time_zone)
9 |
10 | .. code-block:: python
11 |
12 | from coo import Coo
13 |
14 | at = Coo(
15 | "consumer_key",
16 | "consumer_secret",
17 | "access_token",
18 | "access_token_secret"
19 | )
20 |
21 | tweets = [
22 | "My first awesome Twitter Update",
23 | "My second awesome Twitter Update",
24 | "My third awesome Twitter Update",
25 | "My fourth awesome Twitter Update",
26 | "My fifth awesome Twitter Update",
27 | "My sixth awesome Twitter Update",
28 | ]
29 |
30 | # post the twitter updates
31 | at.tweet(tweets)
32 |
33 | Delay
34 | ^^^^^
35 |
36 | You can use ``datetime``, ``date`` and ``time`` strings, integers as seconds and some
37 | `Keywords`_: ``half_hour``, ``one_hour``, ``one_day`` and ``one_week`` between others to
38 | delay the post of your first update.
39 |
40 | .. code-block:: python
41 |
42 | # datetime, date and time strings
43 | at.tweet(tweets, delay="2030-11-24 13:45", time_zone="America/Santiago")
44 | at.tweet(tweets, delay="2030-11-24", time_zone="Australia/Sydney")
45 | at.tweet(tweets, delay="13:45", time_zone="America/New_York")
46 |
47 | # "keywords"
48 | at.tweet(tweets, delay="one_week")
49 |
50 | # integer
51 | at.tweet(tweets, delay=604800)
52 |
53 | .. note::
54 |
55 | When parsing DateTime strings:
56 |
57 | - If a time zone is not specified, it will set to `local`.
58 | - The time will be set to 00:00:00 if it's not specified.
59 | - When passing only time information the date will default to today.
60 | - A future date is needed, otherwise a `ScheduleError` is raised.
61 |
62 | Here you can find all the `Time Zones `_.
63 |
64 | Interval
65 | ^^^^^^^^
66 |
67 | Use integers as seconds or some strings as `Keywords`_: ``half_hour``, ``one_hour``,
68 | ``one_day`` and ``one_week`` between others.
69 |
70 | .. code-block:: python
71 |
72 | # "keywords"
73 | at.tweet(tweets, interval="four_hours")
74 |
75 | # integers
76 | at.tweet(tweets, interval=14400)
77 |
78 | Media files
79 | ^^^^^^^^^^^
80 |
81 | Use one media file for all of your updates:
82 |
83 | .. code-block:: python
84 |
85 | at.tweet(tweets, media="path/to/media.jpeg")
86 |
87 | Random updates
88 | ^^^^^^^^^^^^^^
89 |
90 | To tweet your updates randomly:
91 |
92 | .. code-block:: python
93 |
94 | at.tweet(tweets, aleatory=True)
95 |
96 | Keywords
97 | ^^^^^^^^
98 |
99 | ================ =======
100 | Keyword Seconds
101 | ================ =======
102 | now 0
103 | half_hour 1800
104 | one_hour 3600
105 | two_hours 7200
106 | four_hours 14400
107 | six_hours 21600
108 | eight_hours 28800
109 | ten_hours 36000
110 | twelve_hours 43200
111 | fourteen_hours 50400
112 | sixteen_hours 57600
113 | eighteen_hours 64800
114 | twenty_hours 72000
115 | twenty_two_hours 79200
116 | one_day 86400
117 | two_days 172800
118 | three_days 259200
119 | four_days 345600
120 | five_days 432000
121 | six_days 518400
122 | one_week 604800
123 | ================ =======
124 |
125 | Template
126 | ^^^^^^^^
127 | You can also set one template for each one of the updates.
128 |
129 | .. code-block:: python
130 |
131 | at.tweet(tweets, template=template)
132 |
133 | Templates are very simple, just use a multiline string and add a `$message` where you want your message to appear.
134 |
135 | .. code-block:: python
136 |
137 | template = """My aswesome header
138 |
139 | $message
140 |
141 | #python #coding #coo
142 | """
--------------------------------------------------------------------------------
/docs/source/schedule.rst:
--------------------------------------------------------------------------------
1 | Schedule Twitter Updates
2 | ========================
3 |
4 | Schedule updates with `datetime` strings or integers and use custom `Templates`_ if needed.
5 |
6 | .. code-block:: python
7 |
8 | Coo.schedule(updates, time_zone)
9 |
10 | Full example:
11 |
12 | .. code-block:: python
13 |
14 | from coo import Coo
15 |
16 | at = Coo(
17 | "consumer_key",
18 | "consumer_secret",
19 | "access_token",
20 | "access_token_secret"
21 | )
22 |
23 | tweets = [
24 | # datetime with and without templates
25 | ("2030-10-28 18:50", template, "My Twitter update with a template."),
26 | ("2030-10-29 18:15", template2, "Update with a different template."),
27 | ("2030-11-01 13:45", None, "Twitter update without a template."),
28 |
29 | # date with and without templates
30 | ("2030-12-25", template3, "Merry christmas!"),
31 | ("2031-01-01", None, "And a happy new year!"),
32 |
33 | # time with and without templates
34 | ("18:46", template2, "Will be post today at 18:46."),
35 | ("23:00", None, "A tweet for today at 23:00."),
36 |
37 | # integer (seconds) with and without templates
38 | (3600, template, "This tweet will be posted in an hour."),
39 | (86400, None, "This one, tomorrow at the same hour."),
40 | ]
41 |
42 | at.schedule(tweets, time_zone="America/Santiago")
43 |
44 | Parsing DateTime strings
45 | ^^^^^^^^^^^^^^^^^^^^^^^^
46 |
47 | .. note::
48 |
49 | - If a time zone is not specified, it will set to `local`.
50 | - The time will be set to 00:00:00 if it's not specified.
51 | - When passing only time information the date will default to today.
52 | - A future date is needed, otherwise a `ScheduleError` is raised.
53 |
54 | Here you can find all the `Time Zones `_.
55 |
56 | Media Files
57 | ^^^^^^^^^^^
58 |
59 | There are two ways to add media files to your tweets. The first and easiest is to use one global file for all the updates:
60 |
61 | .. code-block:: python
62 |
63 | at.schedule(tweets, time_zone="America/Santiago", media="path/to/file.png")
64 |
65 | Also, an individual file can be set for each one of the updates:
66 |
67 | .. code-block:: python
68 |
69 | tweets = [
70 | ("2030-10-28 18:50", template, "Update with an image.", "pics/owl.png"),
71 | ("2030-10-29 18:15", template, "Update with other media.", "videos/funny_video.mp4"),
72 | ("2030-11-01 13:45", template, "Tweet without media."),
73 | ]
74 |
75 | Finally, it is possible to combine these to ways. For example, if most of the tweets are gonna use the same media and just a few will have a different or none:
76 |
77 | .. code-block:: python
78 |
79 | tweets = [
80 | ("2030-11-01 13:45", template, "Tweet with global media."),
81 | ("2030-11-02 13:45", template, "Tweet with global media."),
82 | ("2030-11-03 13:45", template, "Tweet with global media."),
83 | ("2030-11-04 13:45", template, "Tweet with global media."),
84 | ("2030-11-05 13:45", template, "Tweet with global media."),
85 | ("2030-11-06 13:45", template, "Tweet with global media."),
86 | ("2030-11-07 13:45", template, "Tweet with global media."),
87 | ("2030-11-08 13:45", template, "Tweet without media.", None),
88 | ("2030-11-09 13:45", template, "Tweet without media.", None),
89 | ("2030-12-10 18:50", template, "Update with an image.", "pics/owl.png"),
90 | ("2030-12-11 18:15", template, "Update with other media.", "videos/funny_video.mp4"),
91 | ]
92 |
93 | at.schedule(tweets, time_zone="America/Santiago", media="path/to/global_media.png")
94 |
95 | Templates
96 | ^^^^^^^^^
97 |
98 | You can set different templates for each one of your updates, or none.
99 |
100 | .. code-block:: python
101 |
102 | tweets = [
103 | # datetime with and without templates
104 | ("2030-10-28 18:50", template, "My Twitter update with a template."),
105 | ("2030-10-29 18:15", template2, "Update with a different template."),
106 | ("2030-11-01 13:45", None, "Twitter update without a template."),
107 |
108 | ]
109 |
110 | at.schedule(tweets, time_zone="America/Santiago")
111 |
112 | Templates are very simple, just use a multiline string and add a `$message` where you want your message to appear.
113 |
114 | .. code-block:: python
115 |
116 | template = """My aswesome header
117 |
118 | $message
119 |
120 | #python #coding #coo
121 | """
--------------------------------------------------------------------------------
/docs/source/twitter_api.rst:
--------------------------------------------------------------------------------
1 | The Twitter API
2 | ===============
3 |
4 | Coo is written using the `Python Twitter `_
5 | wrapper, and through `Coo.api` you gain access to all of his models:
6 |
7 | .. code-block:: python
8 |
9 | # get your followers
10 | followers = at.api.GetFollowers()
11 |
12 | # get your direct messages
13 | d_messages = at.api.GetDirectMessages()
14 |
15 | # favorited tweets
16 | favorites = at.api.GetFavorites()
17 |
18 | # mentions
19 | mentions = at.api.GetMentions()
20 |
21 | # retweets
22 | retweets = at.api.GetRetweets()
23 |
24 | And a lot more. If you are interested, check their `documentation `_.
25 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | category = "dev"
3 | description = "A configurable sidebar-enabled Sphinx theme"
4 | name = "alabaster"
5 | optional = false
6 | python-versions = "*"
7 | version = "0.7.12"
8 |
9 | [[package]]
10 | category = "dev"
11 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
12 | name = "appdirs"
13 | optional = false
14 | python-versions = "*"
15 | version = "1.4.3"
16 |
17 | [[package]]
18 | category = "dev"
19 | description = "Atomic file writes."
20 | name = "atomicwrites"
21 | optional = false
22 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
23 | version = "1.3.0"
24 |
25 | [[package]]
26 | category = "dev"
27 | description = "Classes Without Boilerplate"
28 | name = "attrs"
29 | optional = false
30 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
31 | version = "19.3.0"
32 |
33 | [package.extras]
34 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
35 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
36 | docs = ["sphinx", "zope.interface"]
37 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
38 |
39 | [[package]]
40 | category = "dev"
41 | description = "Internationalization utilities"
42 | name = "babel"
43 | optional = false
44 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
45 | version = "2.8.0"
46 |
47 | [package.dependencies]
48 | pytz = ">=2015.7"
49 |
50 | [[package]]
51 | category = "dev"
52 | description = "The uncompromising code formatter."
53 | name = "black"
54 | optional = false
55 | python-versions = ">=3.6"
56 | version = "19.10b0"
57 |
58 | [package.dependencies]
59 | appdirs = "*"
60 | attrs = ">=18.1.0"
61 | click = ">=6.5"
62 | pathspec = ">=0.6,<1"
63 | regex = "*"
64 | toml = ">=0.9.4"
65 | typed-ast = ">=1.4.0"
66 |
67 | [package.extras]
68 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
69 |
70 | [[package]]
71 | category = "main"
72 | description = "Python package for providing Mozilla's CA Bundle."
73 | name = "certifi"
74 | optional = false
75 | python-versions = "*"
76 | version = "2019.11.28"
77 |
78 | [[package]]
79 | category = "main"
80 | description = "Universal encoding detector for Python 2 and 3"
81 | name = "chardet"
82 | optional = false
83 | python-versions = "*"
84 | version = "3.0.4"
85 |
86 | [[package]]
87 | category = "dev"
88 | description = "Composable command line interface toolkit"
89 | name = "click"
90 | optional = false
91 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
92 | version = "7.1.1"
93 |
94 | [[package]]
95 | category = "dev"
96 | description = "Cross-platform colored terminal text."
97 | marker = "sys_platform == \"win32\" and python_version != \"3.4\" or sys_platform == \"win32\""
98 | name = "colorama"
99 | optional = false
100 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
101 | version = "0.4.3"
102 |
103 | [[package]]
104 | category = "dev"
105 | description = "Docutils -- Python Documentation Utilities"
106 | name = "docutils"
107 | optional = false
108 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
109 | version = "0.16"
110 |
111 | [[package]]
112 | category = "dev"
113 | description = "Discover and load entry points from installed packages."
114 | name = "entrypoints"
115 | optional = false
116 | python-versions = ">=2.7"
117 | version = "0.3"
118 |
119 | [[package]]
120 | category = "dev"
121 | description = "the modular source code checker: pep8, pyflakes and co"
122 | name = "flake8"
123 | optional = false
124 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
125 | version = "3.7.9"
126 |
127 | [package.dependencies]
128 | entrypoints = ">=0.3.0,<0.4.0"
129 | mccabe = ">=0.6.0,<0.7.0"
130 | pycodestyle = ">=2.5.0,<2.6.0"
131 | pyflakes = ">=2.1.0,<2.2.0"
132 |
133 | [[package]]
134 | category = "main"
135 | description = "Clean single-source support for Python 3 and 2"
136 | name = "future"
137 | optional = false
138 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
139 | version = "0.18.2"
140 |
141 | [[package]]
142 | category = "main"
143 | description = "Internationalized Domain Names in Applications (IDNA)"
144 | name = "idna"
145 | optional = false
146 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
147 | version = "2.9"
148 |
149 | [[package]]
150 | category = "dev"
151 | description = "Getting image size from png/jpeg/jpeg2000/gif file"
152 | name = "imagesize"
153 | optional = false
154 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
155 | version = "1.2.0"
156 |
157 | [[package]]
158 | category = "dev"
159 | description = "Read metadata from Python packages"
160 | marker = "python_version < \"3.8\""
161 | name = "importlib-metadata"
162 | optional = false
163 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
164 | version = "1.6.0"
165 |
166 | [package.dependencies]
167 | zipp = ">=0.5"
168 |
169 | [package.extras]
170 | docs = ["sphinx", "rst.linker"]
171 | testing = ["packaging", "importlib-resources"]
172 |
173 | [[package]]
174 | category = "dev"
175 | description = "A very fast and expressive template engine."
176 | name = "jinja2"
177 | optional = false
178 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
179 | version = "2.11.1"
180 |
181 | [package.dependencies]
182 | MarkupSafe = ">=0.23"
183 |
184 | [package.extras]
185 | i18n = ["Babel (>=0.8)"]
186 |
187 | [[package]]
188 | category = "dev"
189 | description = "Safely add untrusted strings to HTML/XML markup."
190 | name = "markupsafe"
191 | optional = false
192 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
193 | version = "1.1.1"
194 |
195 | [[package]]
196 | category = "dev"
197 | description = "McCabe checker, plugin for flake8"
198 | name = "mccabe"
199 | optional = false
200 | python-versions = "*"
201 | version = "0.6.1"
202 |
203 | [[package]]
204 | category = "dev"
205 | description = "More routines for operating on iterables, beyond itertools"
206 | marker = "python_version > \"2.7\""
207 | name = "more-itertools"
208 | optional = false
209 | python-versions = ">=3.5"
210 | version = "8.2.0"
211 |
212 | [[package]]
213 | category = "main"
214 | description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
215 | name = "oauthlib"
216 | optional = false
217 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
218 | version = "3.1.0"
219 |
220 | [package.extras]
221 | rsa = ["cryptography"]
222 | signals = ["blinker"]
223 | signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
224 |
225 | [[package]]
226 | category = "dev"
227 | description = "Core utilities for Python packages"
228 | name = "packaging"
229 | optional = false
230 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
231 | version = "20.3"
232 |
233 | [package.dependencies]
234 | pyparsing = ">=2.0.2"
235 | six = "*"
236 |
237 | [[package]]
238 | category = "dev"
239 | description = "Utility library for gitignore style pattern matching of file paths."
240 | name = "pathspec"
241 | optional = false
242 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
243 | version = "0.7.0"
244 |
245 | [[package]]
246 | category = "main"
247 | description = "Python datetimes made easy"
248 | name = "pendulum"
249 | optional = false
250 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
251 | version = "2.1.0"
252 |
253 | [package.dependencies]
254 | python-dateutil = ">=2.6,<3.0"
255 | pytzdata = ">=2018.3"
256 |
257 | [[package]]
258 | category = "dev"
259 | description = "plugin and hook calling mechanisms for python"
260 | name = "pluggy"
261 | optional = false
262 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
263 | version = "0.13.1"
264 |
265 | [package.dependencies]
266 | [package.dependencies.importlib-metadata]
267 | python = "<3.8"
268 | version = ">=0.12"
269 |
270 | [package.extras]
271 | dev = ["pre-commit", "tox"]
272 |
273 | [[package]]
274 | category = "dev"
275 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
276 | name = "py"
277 | optional = false
278 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
279 | version = "1.8.1"
280 |
281 | [[package]]
282 | category = "dev"
283 | description = "Python style guide checker"
284 | name = "pycodestyle"
285 | optional = false
286 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
287 | version = "2.5.0"
288 |
289 | [[package]]
290 | category = "dev"
291 | description = "passive checker of Python programs"
292 | name = "pyflakes"
293 | optional = false
294 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
295 | version = "2.1.1"
296 |
297 | [[package]]
298 | category = "dev"
299 | description = "Pygments is a syntax highlighting package written in Python."
300 | name = "pygments"
301 | optional = false
302 | python-versions = ">=3.5"
303 | version = "2.6.1"
304 |
305 | [[package]]
306 | category = "dev"
307 | description = "Python parsing module"
308 | name = "pyparsing"
309 | optional = false
310 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
311 | version = "2.4.6"
312 |
313 | [[package]]
314 | category = "dev"
315 | description = "pytest: simple powerful testing with Python"
316 | name = "pytest"
317 | optional = false
318 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
319 | version = "4.6.9"
320 |
321 | [package.dependencies]
322 | atomicwrites = ">=1.0"
323 | attrs = ">=17.4.0"
324 | packaging = "*"
325 | pluggy = ">=0.12,<1.0"
326 | py = ">=1.5.0"
327 | six = ">=1.10.0"
328 | wcwidth = "*"
329 |
330 | [package.dependencies.colorama]
331 | python = "<3.4.0 || >=3.5.0"
332 | version = "*"
333 |
334 | [package.dependencies.importlib-metadata]
335 | python = "<3.8"
336 | version = ">=0.12"
337 |
338 | [package.dependencies.more-itertools]
339 | python = ">=2.8"
340 | version = ">=4.0.0"
341 |
342 | [package.extras]
343 | testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"]
344 |
345 | [[package]]
346 | category = "main"
347 | description = "Extensions to the standard Python datetime module"
348 | name = "python-dateutil"
349 | optional = false
350 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
351 | version = "2.8.1"
352 |
353 | [package.dependencies]
354 | six = ">=1.5"
355 |
356 | [[package]]
357 | category = "main"
358 | description = "A Python wrapper around the Twitter API"
359 | name = "python-twitter"
360 | optional = false
361 | python-versions = "*"
362 | version = "3.5"
363 |
364 | [package.dependencies]
365 | future = "*"
366 | requests = "*"
367 | requests-oauthlib = "*"
368 |
369 | [[package]]
370 | category = "dev"
371 | description = "World timezone definitions, modern and historical"
372 | name = "pytz"
373 | optional = false
374 | python-versions = "*"
375 | version = "2019.3"
376 |
377 | [[package]]
378 | category = "main"
379 | description = "The Olson timezone database for Python."
380 | name = "pytzdata"
381 | optional = false
382 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
383 | version = "2019.3"
384 |
385 | [[package]]
386 | category = "dev"
387 | description = "Alternative regular expression module, to replace re."
388 | name = "regex"
389 | optional = false
390 | python-versions = "*"
391 | version = "2020.2.20"
392 |
393 | [[package]]
394 | category = "main"
395 | description = "Python HTTP for Humans."
396 | name = "requests"
397 | optional = false
398 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
399 | version = "2.23.0"
400 |
401 | [package.dependencies]
402 | certifi = ">=2017.4.17"
403 | chardet = ">=3.0.2,<4"
404 | idna = ">=2.5,<3"
405 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
406 |
407 | [package.extras]
408 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
409 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
410 |
411 | [[package]]
412 | category = "main"
413 | description = "OAuthlib authentication support for Requests."
414 | name = "requests-oauthlib"
415 | optional = false
416 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
417 | version = "1.3.0"
418 |
419 | [package.dependencies]
420 | oauthlib = ">=3.0.0"
421 | requests = ">=2.0.0"
422 |
423 | [package.extras]
424 | rsa = ["oauthlib (>=3.0.0)"]
425 |
426 | [[package]]
427 | category = "main"
428 | description = "Python 2 and 3 compatibility utilities"
429 | name = "six"
430 | optional = false
431 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
432 | version = "1.14.0"
433 |
434 | [[package]]
435 | category = "dev"
436 | description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
437 | name = "snowballstemmer"
438 | optional = false
439 | python-versions = "*"
440 | version = "2.0.0"
441 |
442 | [[package]]
443 | category = "dev"
444 | description = "Python documentation generator"
445 | name = "sphinx"
446 | optional = false
447 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
448 | version = "1.8.5"
449 |
450 | [package.dependencies]
451 | Jinja2 = ">=2.3"
452 | Pygments = ">=2.0"
453 | alabaster = ">=0.7,<0.8"
454 | babel = ">=1.3,<2.0 || >2.0"
455 | colorama = ">=0.3.5"
456 | docutils = ">=0.11"
457 | imagesize = "*"
458 | packaging = "*"
459 | requests = ">=2.0.0"
460 | setuptools = "*"
461 | six = ">=1.5"
462 | snowballstemmer = ">=1.1"
463 | sphinxcontrib-websupport = "*"
464 |
465 | [package.extras]
466 | test = ["mock", "pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "enum34", "mypy", "typed-ast"]
467 | websupport = ["sqlalchemy (>=0.9)", "whoosh (>=2.0)"]
468 |
469 | [[package]]
470 | category = "dev"
471 | description = "Sphinx API for Web Apps"
472 | name = "sphinxcontrib-websupport"
473 | optional = false
474 | python-versions = ">=3.5"
475 | version = "1.2.1"
476 |
477 | [package.extras]
478 | lint = ["flake8"]
479 | test = ["pytest", "sqlalchemy", "whoosh", "sphinx"]
480 |
481 | [[package]]
482 | category = "dev"
483 | description = "Python Library for Tom's Obvious, Minimal Language"
484 | name = "toml"
485 | optional = false
486 | python-versions = "*"
487 | version = "0.10.0"
488 |
489 | [[package]]
490 | category = "dev"
491 | description = "a fork of Python 2 and 3 ast modules with type comment support"
492 | name = "typed-ast"
493 | optional = false
494 | python-versions = "*"
495 | version = "1.4.1"
496 |
497 | [[package]]
498 | category = "main"
499 | description = "HTTP library with thread-safe connection pooling, file post, and more."
500 | name = "urllib3"
501 | optional = false
502 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
503 | version = "1.25.8"
504 |
505 | [package.extras]
506 | brotli = ["brotlipy (>=0.6.0)"]
507 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
508 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
509 |
510 | [[package]]
511 | category = "dev"
512 | description = "Measures number of Terminal column cells of wide-character codes"
513 | name = "wcwidth"
514 | optional = false
515 | python-versions = "*"
516 | version = "0.1.9"
517 |
518 | [[package]]
519 | category = "dev"
520 | description = "Backport of pathlib-compatible object wrapper for zip files"
521 | marker = "python_version < \"3.8\""
522 | name = "zipp"
523 | optional = false
524 | python-versions = ">=3.6"
525 | version = "3.1.0"
526 |
527 | [package.extras]
528 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
529 | testing = ["jaraco.itertools", "func-timeout"]
530 |
531 | [metadata]
532 | content-hash = "967efad63d76f273668e8c13a20d219227f90be1f4a908041b46029de92b1ef1"
533 | python-versions = "^3.7"
534 |
535 | [metadata.files]
536 | alabaster = [
537 | {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
538 | {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
539 | ]
540 | appdirs = [
541 | {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
542 | {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
543 | ]
544 | atomicwrites = [
545 | {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
546 | {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
547 | ]
548 | attrs = [
549 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
550 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
551 | ]
552 | babel = [
553 | {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
554 | {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
555 | ]
556 | black = [
557 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
558 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
559 | ]
560 | certifi = [
561 | {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
562 | {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
563 | ]
564 | chardet = [
565 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
566 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
567 | ]
568 | click = [
569 | {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"},
570 | {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"},
571 | ]
572 | colorama = [
573 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
574 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
575 | ]
576 | docutils = [
577 | {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
578 | {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
579 | ]
580 | entrypoints = [
581 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
582 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
583 | ]
584 | flake8 = [
585 | {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
586 | {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
587 | ]
588 | future = [
589 | {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
590 | ]
591 | idna = [
592 | {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
593 | {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
594 | ]
595 | imagesize = [
596 | {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
597 | {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
598 | ]
599 | importlib-metadata = [
600 | {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"},
601 | {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"},
602 | ]
603 | jinja2 = [
604 | {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"},
605 | {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"},
606 | ]
607 | markupsafe = [
608 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
609 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
610 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
611 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
612 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
613 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
614 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
615 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
616 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
617 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
618 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
619 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
620 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
621 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
622 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
623 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
624 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
625 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
626 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
627 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
628 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
629 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
630 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
631 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
632 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
633 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
634 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
635 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
636 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
637 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
638 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
639 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
640 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
641 | ]
642 | mccabe = [
643 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
644 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
645 | ]
646 | more-itertools = [
647 | {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
648 | {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
649 | ]
650 | oauthlib = [
651 | {file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"},
652 | {file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
653 | ]
654 | packaging = [
655 | {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"},
656 | {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"},
657 | ]
658 | pathspec = [
659 | {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
660 | {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},
661 | ]
662 | pendulum = [
663 | {file = "pendulum-2.1.0-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:9eda38ff65b1f297d860d3f562480e048673fb4b81fdd5c8c55decb519b97ed2"},
664 | {file = "pendulum-2.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:70007aebc4494163f8705909a1996ce21ab853801b57fba4c2dd53c3df5c38f0"},
665 | {file = "pendulum-2.1.0-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:575934b65b298eeb99c5a5b1673c945fc5c99e2b56caff772a91bc4b1eba7b82"},
666 | {file = "pendulum-2.1.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:d42d1e870541eeaf3fe0500aac0c76a85bd4bd53ebed74f9a7daf8f01ac77374"},
667 | {file = "pendulum-2.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff7f3420de0c0cf21c1fc813d581fcfa4a1fb6d87f09485880b3e1204eb9cdd7"},
668 | {file = "pendulum-2.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:ac3c6a992beeb4c9bd90c317a1bb2a6cba159b49a49b6dd3c86b5bacb86f3d50"},
669 | {file = "pendulum-2.1.0-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:75a62e3f98499283fafe8ef4b44f81052e84825b00a0b64609dd8a06985382b9"},
670 | {file = "pendulum-2.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a79a72a7fd1092a7c69ddd8580a0be5365ded40c9f9c865623c7665742e3b888"},
671 | {file = "pendulum-2.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:701127e1f0ff7c253cc0c07f29becc5f9210547914e0bbe59ffd9fa064d7c3c8"},
672 | {file = "pendulum-2.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:816e01dcb0ba4ffcf2ceaafe4d644174fea680361e909f6f8ba0a4fdb2ccae24"},
673 | {file = "pendulum-2.1.0-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:edd00e6b43698762e10bfda508cc9c06bad88c0703a9b37e412aec1189e06e23"},
674 | {file = "pendulum-2.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4420e058110740a8193fb0709350dfc6ac790a99c345fc4e92e24df0f834ddcb"},
675 | {file = "pendulum-2.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:aa560bd39d94f3889646422f1e65b8dfd025bf6288d43e5c2e31d4f972aaf2e4"},
676 | {file = "pendulum-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2788945a0111d5325fd27ae3e3b18b741e440d20bdb7d4ea22fce7c9a4fbbf40"},
677 | {file = "pendulum-2.1.0-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:eb7e349bb2d1b2b418d094e2179d6768561e8242fd8cb640b5aaba735f3e91d1"},
678 | {file = "pendulum-2.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6cf0f876cd088ee1578266f4231121376747aa90c3ed3b8e212a8344a9920061"},
679 | {file = "pendulum-2.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:aa13ddea12fd871d3191f633f08090b91ea2e80fb0ed50a7a149add7f680b12d"},
680 | {file = "pendulum-2.1.0-cp38-cp38m-win_amd64.whl", hash = "sha256:0cbbd4f30c69a283690d9ed8e58e44a990e067e59ee05b5ef55d022b38659aeb"},
681 | {file = "pendulum-2.1.0.tar.gz", hash = "sha256:093cab342e10516660e64b935a6da1a043e0286de36cc229fb48471415981ffe"},
682 | ]
683 | pluggy = [
684 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
685 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
686 | ]
687 | py = [
688 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"},
689 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"},
690 | ]
691 | pycodestyle = [
692 | {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
693 | {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
694 | ]
695 | pyflakes = [
696 | {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
697 | {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
698 | ]
699 | pygments = [
700 | {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
701 | {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
702 | ]
703 | pyparsing = [
704 | {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
705 | {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
706 | ]
707 | pytest = [
708 | {file = "pytest-4.6.9-py2.py3-none-any.whl", hash = "sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"},
709 | {file = "pytest-4.6.9.tar.gz", hash = "sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339"},
710 | ]
711 | python-dateutil = [
712 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
713 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
714 | ]
715 | python-twitter = [
716 | {file = "python-twitter-3.5.tar.gz", hash = "sha256:45855742f1095aa0c8c57b2983eee3b6b7f527462b50a2fa8437a8b398544d90"},
717 | {file = "python_twitter-3.5-py2.py3-none-any.whl", hash = "sha256:4a420a6cb6ee9d0c8da457c8a8573f709c2ff2e1a7542e2d38807ebbfe8ebd1d"},
718 | ]
719 | pytz = [
720 | {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"},
721 | {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
722 | ]
723 | pytzdata = [
724 | {file = "pytzdata-2019.3-py2.py3-none-any.whl", hash = "sha256:84c52b9a47d097fcd483f047a544979de6c3a86e94c845e3569e9f8acd0fa071"},
725 | {file = "pytzdata-2019.3.tar.gz", hash = "sha256:fac06f7cdfa903188dc4848c655e4adaee67ee0f2fe08e7daf815cf2a761ee5e"},
726 | ]
727 | regex = [
728 | {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"},
729 | {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"},
730 | {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"},
731 | {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"},
732 | {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"},
733 | {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"},
734 | {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"},
735 | {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"},
736 | {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"},
737 | {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"},
738 | {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"},
739 | {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"},
740 | {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"},
741 | {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"},
742 | {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"},
743 | {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"},
744 | {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"},
745 | {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"},
746 | {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"},
747 | {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"},
748 | {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"},
749 | ]
750 | requests = [
751 | {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
752 | {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
753 | ]
754 | requests-oauthlib = [
755 | {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
756 | {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
757 | {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
758 | ]
759 | six = [
760 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
761 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
762 | ]
763 | snowballstemmer = [
764 | {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
765 | {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
766 | ]
767 | sphinx = [
768 | {file = "Sphinx-1.8.5-py2.py3-none-any.whl", hash = "sha256:9f3e17c64b34afc653d7c5ec95766e03043cc6d80b0de224f59b6b6e19d37c3c"},
769 | {file = "Sphinx-1.8.5.tar.gz", hash = "sha256:c7658aab75c920288a8cf6f09f244c6cfdae30d82d803ac1634d9f223a80ca08"},
770 | ]
771 | sphinxcontrib-websupport = [
772 | {file = "sphinxcontrib-websupport-1.2.1.tar.gz", hash = "sha256:545f5da4bd7757e82b8a23ce3af9500c6ffeedbcb13429fca436ad1e80bd10cf"},
773 | {file = "sphinxcontrib_websupport-1.2.1-py2.py3-none-any.whl", hash = "sha256:69364896eae5d1145d82b6ee09f66d597099ef8069615e2888921ec48005470f"},
774 | ]
775 | toml = [
776 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
777 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
778 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
779 | ]
780 | typed-ast = [
781 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
782 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
783 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
784 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
785 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
786 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
787 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
788 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
789 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
790 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
791 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
792 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
793 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
794 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
795 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
796 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
797 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
798 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
799 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
800 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
801 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
802 | ]
803 | urllib3 = [
804 | {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
805 | {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
806 | ]
807 | wcwidth = [
808 | {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"},
809 | {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"},
810 | ]
811 | zipp = [
812 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
813 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
814 | ]
815 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "coo"
3 | version = "0.1.2"
4 | description = "schedule Twitter updates with easy"
5 | authors = ["wilfredinni"]
6 | license = "MIT"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.7"
10 | pendulum = "^2.0"
11 | python-twitter = "^3.5"
12 |
13 | [tool.poetry.dev-dependencies]
14 | black = { version = "*", allow-prereleases = true }
15 | flake8 = "^3.6"
16 | pytest = "^4.0"
17 | sphinx = "^1.8"
18 |
19 | [build-system]
20 | requires = ["poetry>=0.12"]
21 | build-backend = "poetry.masonry.api"
22 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pendulum
2 | python-twitter
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import re
3 |
4 | with open("coo/__init__.py", "r") as file:
5 | regex_version = r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]'
6 | version = re.search(regex_version, file.read(), re.MULTILINE).group(1)
7 |
8 | with open("README_P.rst") as readme_file:
9 | readme = readme_file.read()
10 |
11 |
12 | requirements = ["python-twitter", "pendulum"]
13 |
14 | test_requirements = ["pytest", "flake8", "flake8-mypy", "black"]
15 |
16 | setup(
17 | author="Carlos Montecinos Geisse",
18 | author_email="carlos.w.montecinos@gmail.com",
19 | classifiers=[
20 | "Development Status :: 2 - Pre-Alpha",
21 | "Intended Audience :: Developers",
22 | "License :: OSI Approved :: Apache Software License",
23 | "Natural Language :: English",
24 | "Programming Language :: Python :: 3.6",
25 | "Programming Language :: Python :: 3.7",
26 | ],
27 | description="Schedule Twitter Updates with Easy",
28 | install_requires=requirements,
29 | license="MIT License",
30 | long_description=readme,
31 | include_package_data=True,
32 | keywords="coo",
33 | name="coo",
34 | packages=find_packages(include=["coo"]),
35 | test_suite="tests",
36 | tests_require=test_requirements,
37 | url="https://github.com/wilfredinni/coo",
38 | version=version,
39 | zip_safe=False,
40 | )
41 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wilfredinni/coo/e6dbfc11aadfdde27c6edbd0343adb54b849a5fc/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_coo.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from pathlib import Path
3 |
4 | import pytest
5 | from twitter.error import TwitterError
6 |
7 | from coo import Coo
8 | from coo._exceptions import TweetTypeError, ScheduleError
9 |
10 |
11 | # Mock Update list
12 | m_updates = ["mock1", "mock2", "mock3", "mock4", "mock5"]
13 |
14 |
15 | def test_input_types():
16 | with pytest.raises(TypeError):
17 | Coo([1], [1], [1], [1])
18 |
19 | with pytest.raises(TypeError):
20 | Coo({1: 5}, {1: 5}, {1: 5}, {1: 5})
21 |
22 | with pytest.raises(TypeError):
23 | Coo(1, 2, 3, 4)
24 |
25 | # correct construction. no error
26 | Coo("mock", "mock", "mock", "mock")
27 |
28 |
29 | @pytest.fixture
30 | def coo_preview_instance():
31 | yield Coo("mock", "mock", "mock", "mock", preview=True)
32 |
33 |
34 | @pytest.fixture
35 | def coo_mock_instance():
36 | yield Coo("mock1", "mock2", "mock3", "mock4")
37 |
38 |
39 | # API
40 | def test_wrong_credentials_TwitterError(coo_mock_instance):
41 | with pytest.raises(TwitterError):
42 | coo_mock_instance.verify
43 |
44 |
45 | # TWEET
46 | @pytest.mark.parametrize(
47 | "updates, delay, interval, template, time_zone",
48 | [
49 | (m_updates, None, None, None, None),
50 | # One None
51 | (m_updates, None, "now", "$message", "local"),
52 | (m_updates, "now", None, "$message", "local"),
53 | (m_updates, "now", "now", None, "local"),
54 | (m_updates, "now", "now", "$message", None),
55 | # Two None
56 | (m_updates, None, None, "$message", "local"),
57 | (m_updates, "now", None, None, "local"),
58 | (m_updates, "now", "now", None, None),
59 | (m_updates, None, "now", "$message", None),
60 | # _delay
61 | (m_updates, "now", None, None, None),
62 | (m_updates, 0, None, None, None),
63 | # _interval
64 | (m_updates, None, "now", None, None),
65 | (m_updates, None, 0, None, None),
66 | # Template
67 | (m_updates, None, None, "$message", None),
68 | # Time zone
69 | (m_updates, None, None, None, "local"),
70 | (m_updates, None, None, None, "America/Santiago "),
71 | ],
72 | )
73 | def test_tweet(coo_preview_instance, updates, delay, interval, template, time_zone):
74 | coo_preview_instance.tweet(updates, delay, interval, template, time_zone)
75 |
76 |
77 | @pytest.mark.parametrize(
78 | "tz",
79 | [
80 | ("Canada/Yukon"),
81 | ("Brazil/Acre"),
82 | ("Australia/Tasmania"),
83 | ("America/Santiago"),
84 | ("America/Detroit"),
85 | ("Asia/Atyrau"),
86 | ],
87 | )
88 | def test_tweet_time_zone(coo_preview_instance, tz):
89 | coo_preview_instance.tweet(["mock"], time_zone=tz)
90 | assert coo_preview_instance.time_zone == tz
91 |
92 |
93 | def test_tweet_random(coo_preview_instance):
94 | updates = ["mock1", "mock2", "mock3", "mock4", "mock5"]
95 | coo_preview_instance.tweet(m_updates, aleatory=True)
96 | assert updates != m_updates
97 |
98 |
99 | def test_tweet_media_update(coo_preview_instance):
100 | coo_preview_instance.tweet(["mock"], media="../coo.png")
101 | assert coo_preview_instance.media == Path("../coo.png")
102 |
103 |
104 | @pytest.mark.parametrize(
105 | "updates",
106 | [
107 | # update is not a instance of list:
108 | ((1, 2, 3)),
109 | ({1, 2, 3}),
110 | (123),
111 | ("string"),
112 | # The instances 'in' the list are no strings:
113 | ([(1, 2, 3)]),
114 | ([{1, 2, 3}]),
115 | ([[1, 2, 3]]),
116 | ([1, 2, 3]),
117 | ],
118 | )
119 | def test_tweet_TweetTypeError(coo_preview_instance, updates):
120 | with pytest.raises(TweetTypeError):
121 | coo_preview_instance.tweet(updates)
122 |
123 |
124 | def test_tweet_media_FileNotFoundError(coo_mock_instance):
125 | with pytest.raises(FileNotFoundError):
126 | coo_mock_instance.tweet(["mock"], media="coo_.png")
127 |
128 |
129 | def test_tweet_media_TwitterError(coo_mock_instance):
130 | with pytest.raises(TwitterError):
131 | coo_mock_instance.tweet(["mock"], media="coo.png")
132 |
133 |
134 | def test_tweet_none_media_TwitterError(coo_mock_instance):
135 | with pytest.raises(TwitterError):
136 | coo_mock_instance.tweet(["mock"], media=None)
137 |
138 |
139 | # SCHEDULE
140 | def test_schedule_time_zone_media(coo_preview_instance):
141 | updates = [
142 | ("now", "template", "update"),
143 | (0, "template", "update"),
144 | ("now", None, "update"),
145 | (0, None, "update"),
146 | (0, None, "update", "../coo.png"),
147 | ]
148 | coo_preview_instance.loop = asyncio.new_event_loop()
149 | coo_preview_instance.schedule(updates, time_zone="Canada/Yukon", media="../coo.png")
150 | assert coo_preview_instance.time_zone == "Canada/Yukon"
151 | assert coo_preview_instance.media == Path("../coo.png")
152 | assert coo_preview_instance.global_media == Path("../coo.png")
153 |
154 |
155 | @pytest.mark.parametrize(
156 | "updates",
157 | [
158 | ([["update1", "update2"]]),
159 | ([{"update1", "update2"}]),
160 | (["update1", "update2"]),
161 | ([123, 456, 789]),
162 | # len tuple
163 | ([("now")]),
164 | ],
165 | )
166 | def test_schedule_ScheduleError(coo_preview_instance, updates):
167 | with pytest.raises(ScheduleError):
168 | coo_preview_instance.schedule(updates)
169 |
170 |
171 | # STR UPDATE
172 | @pytest.mark.parametrize(
173 | "update, template", [("My Twitter Update", None), ("My Twitter Update", "$message")]
174 | )
175 | def test__str_update(coo_preview_instance, update, template):
176 | coo_preview_instance._str_update(update, template)
177 |
178 |
179 | # _delay
180 | @pytest.mark.parametrize("delay", [(0), ("now")])
181 | def test_delay(coo_preview_instance, delay):
182 | coo_preview_instance._delay(delay)
183 |
184 |
185 | # _interval
186 | @pytest.mark.parametrize("_interval", [(0), ("now")])
187 | def test__interval(coo_preview_instance, _interval):
188 | coo_preview_instance._interval(_interval)
189 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from coo._utils import parse_time, parse_or_get, zzz, tweet_template, TIME_DICT
4 | from coo._exceptions import ScheduleError, TemplateError
5 |
6 |
7 | # DICTIONARIES
8 | @pytest.mark.parametrize(
9 | "time_delay, int_value",
10 | [
11 | ("now", 0),
12 | ("half_hour", 1800),
13 | ("one_hour", 3600),
14 | ("two_hours", 7200),
15 | ("four_hours", 14400),
16 | ("six_hours", 21600),
17 | ("eight_hours", 28800),
18 | ("ten_hours", 36000),
19 | ("twelve_hours", 43200),
20 | ("fourteen_hours", 50400),
21 | ("sixteen_hours", 57600),
22 | ("eighteen_hours", 64800),
23 | ("twenty_hours", 72000),
24 | ("twenty_two_hours", 79200),
25 | ("one_day", 86400),
26 | ("two_days", 172800),
27 | ("three_days", 259200),
28 | ("four_days", 345600),
29 | ("five_days", 432000),
30 | ("six_days", 518400),
31 | ("one_week", 604800),
32 | ],
33 | )
34 | def test_TIME_DICT(time_delay, int_value):
35 | assert TIME_DICT.get(time_delay) == int_value
36 |
37 |
38 | # PARSE TIME
39 | @pytest.mark.parametrize(
40 | "date_time, time_zone",
41 | [("2040-10-28 18:46", "America/Santiago"), ("2040-10-28", "America/Santiago")],
42 | )
43 | def test_parse_time(date_time, time_zone):
44 | secs = parse_time(date_time, time_zone)
45 | assert isinstance(secs, int)
46 |
47 |
48 | @pytest.mark.parametrize(
49 | "schedule_time, time_zone",
50 | [("2015-10-28 18:46", "America/Santiago"), ("2002-10-28", "America/Santiago")],
51 | )
52 | def test_parse_time_ScheduleError(schedule_time, time_zone):
53 | with pytest.raises(ScheduleError):
54 | parse_time(schedule_time, time_zone)
55 |
56 |
57 | # PARSE OR GET
58 | @pytest.mark.parametrize(
59 | "schedule_time, time_zone",
60 | [
61 | (20, None),
62 | ("now", None),
63 | ("2040-10-28", "America/Santiago"),
64 | ("2040-10-28 18:46", "America/Santiago"),
65 | ],
66 | )
67 | def test_parse_or_get(schedule_time, time_zone):
68 | seconds = parse_or_get(schedule_time, time_zone)
69 | assert isinstance(seconds, int)
70 |
71 |
72 | @pytest.mark.parametrize(
73 | "schedule_time, time_zone",
74 | [
75 | ("wrong_delay_time", "America/Santiago"),
76 | ("wrong_delay_time", "America/Santiago"),
77 | ],
78 | )
79 | def test_parse_or_get_TypeError(schedule_time, time_zone):
80 | with pytest.raises(TypeError):
81 | parse_or_get(schedule_time, time_zone)
82 |
83 |
84 | # ZZZ
85 | @pytest.mark.parametrize("sleep_time, time_zone", [(0, None), ("now", None)])
86 | def test_zzz_INT(sleep_time, time_zone):
87 | zzz(sleep_time, time_zone)
88 |
89 |
90 | # TEMPLATE
91 | def test_tweet_template():
92 | assert isinstance(tweet_template("msg", "$message"), str)
93 |
94 |
95 | @pytest.mark.parametrize(
96 | "update, template",
97 | [
98 | ("msg", None),
99 | ("msg", (1, 2, 3)),
100 | ("msg", [1, 2, 3]),
101 | ("msg", {1, 2, 3}),
102 | ("msg", {1: 2}),
103 | ],
104 | )
105 | def test_tweet_template_TemplateError(update, template):
106 | with pytest.raises(TemplateError):
107 | tweet_template(update, template)
108 |
109 |
110 | def test_message_template_TemplateError():
111 | with pytest.raises(TemplateError):
112 | tweet_template("update", "template")
113 |
--------------------------------------------------------------------------------