feat: add 1.0.0 version

This commit is contained in:
Dmitriy Pleshevskiy 2019-07-23 22:59:53 +03:00
parent a85b42dd58
commit 0a1d77100f
12 changed files with 973 additions and 0 deletions

26
.coveragerc Normal file
View file

@ -0,0 +1,26 @@
[run]
branch = true
source = genrss
omit =
.venv
__meta__.py
[report]
exclude_lines =
pragma: no cover
def __repr__
if self\.debug:
if settings.DEBUG
raise NotImplementedError
if 0:
if False:
if __name__ == .__main__.:
@abstractmethod
fail_under = 80
precision = 2
show_missing = true
skip_covered = true
[html]
directory = tests/htmlcov

155
.gitignore vendored Normal file
View file

@ -0,0 +1,155 @@
# Created by https://www.gitignore.io/api/vim,python
# Edit at https://www.gitignore.io/?templates=vim,python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
### Vim ###
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# End of https://www.gitignore.io/api/vim,python
# IDE
.idea/
.c9/

11
.travis.yml Normal file
View file

@ -0,0 +1,11 @@
language: python
python:
- '3.6'
- '3.7'
install:
- pip install -r requirements-test.txt
- pip install -e .
script:
- coverage run -m pytest
after_success:
- coveralls

16
Pipfile Normal file
View file

@ -0,0 +1,16 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
pytest = "*"
coverage = "*"
coveralls = "*"
[packages]
lxml = "*"
pytz = "*"
[requires]
python_version = "3.7"

377
Pipfile.lock generated Normal file
View file

@ -0,0 +1,377 @@
{
"_meta": {
"hash": {
"sha256": "f0911fe39e4694db48dedc159ec16cbf28529b922b8b1ec5fdbdfd67d70cdcf3"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"lxml": {
"hashes": [
"sha256:06c7616601430aa140a69f97e3116308fffe0848f543b639a5ec2e8920ae72fd",
"sha256:177202792f9842374a8077735c69c41a4282183f7851443d2beb8ee310720819",
"sha256:19317ad721ceb9e39847d11131903931e2794e447d4751ebb0d9236f1b349ff2",
"sha256:36d206e62f3e5dbaafd4ec692b67157e271f5da7fd925fda8515da675eace50d",
"sha256:387115b066c797c85f9861a9613abf50046a15aac16759bc92d04f94acfad082",
"sha256:3ce1c49d4b4a7bc75fb12acb3a6247bb7a91fe420542e6d671ba9187d12a12c2",
"sha256:4d2a5a7d6b0dbb8c37dab66a8ce09a8761409c044017721c21718659fa3365a1",
"sha256:58d0a1b33364d1253a88d18df6c0b2676a1746d27c969dc9e32d143a3701dda5",
"sha256:62a651c618b846b88fdcae0533ec23f185bb322d6c1845733f3123e8980c1d1b",
"sha256:69ff21064e7debc9b1b1e2eee8c2d686d042d4257186d70b338206a80c5bc5ea",
"sha256:7060453eba9ba59d821625c6af6a266bd68277dce6577f754d1eb9116c094266",
"sha256:7d26b36a9c4bce53b9cfe42e67849ae3c5c23558bc08363e53ffd6d94f4ff4d2",
"sha256:83b427ad2bfa0b9705e02a83d8d607d2c2f01889eb138168e462a3a052c42368",
"sha256:923d03c84534078386cf50193057aae98fa94cace8ea7580b74754493fda73ad",
"sha256:b773715609649a1a180025213f67ffdeb5a4878c784293ada300ee95a1f3257b",
"sha256:baff149c174e9108d4a2fee192c496711be85534eab63adb122f93e70aa35431",
"sha256:bca9d118b1014b4c2d19319b10a3ebed508ff649396ce1855e1c96528d9b2fa9",
"sha256:ce580c28845581535dc6000fc7c35fdadf8bea7ccb57d6321b044508e9ba0685",
"sha256:d34923a569e70224d88e6682490e24c842907ba2c948c5fd26185413cbe0cd96",
"sha256:dd9f0e531a049d8b35ec5e6c68a37f1ba6ec3a591415e6804cbdf652793d15d7",
"sha256:ecb805cbfe9102f3fd3d2ef16dfe5ae9e2d7a7dfbba92f4ff1e16ac9784dbfb0",
"sha256:ede9aad2197a0202caff35d417b671f5f91a3631477441076082a17c94edd846",
"sha256:ef2d1fc370400e0aa755aab0b20cf4f1d0e934e7fd5244f3dd4869078e4942b9",
"sha256:f2fec194a49bfaef42a548ee657362af5c7a640da757f6f452a35da7dd9f923c"
],
"index": "pypi",
"version": "==4.3.4"
},
"pytz": {
"hashes": [
"sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda",
"sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"
],
"index": "pypi",
"version": "==2019.1"
}
},
"develop": {
"asn1crypto": {
"hashes": [
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
],
"version": "==0.24.0"
},
"atomicwrites": {
"hashes": [
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
],
"version": "==1.3.0"
},
"attrs": {
"hashes": [
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
],
"version": "==19.1.0"
},
"certifi": {
"hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
],
"version": "==2019.6.16"
},
"cffi": {
"hashes": [
"sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
"sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
"sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
"sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
"sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
"sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
"sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
"sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
"sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
"sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
"sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
"sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
"sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
"sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
"sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
"sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
"sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
"sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
"sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
"sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
"sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
"sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
"sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
"sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
"sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
"sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
"sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
"sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
],
"version": "==1.12.3"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"configparser": {
"hashes": [
"sha256:8be81d89d6e7b4c0d4e44bcc525845f6da25821de80cb5e06e7e0238a2899e32",
"sha256:da60d0014fd8c55eb48c1c5354352e363e2d30bbf7057e5e171a468390184c75"
],
"markers": "python_version < '3'",
"version": "==3.7.4"
},
"contextlib2": {
"hashes": [
"sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48",
"sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"
],
"markers": "python_version < '3'",
"version": "==0.5.5"
},
"coverage": {
"hashes": [
"sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9",
"sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74",
"sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390",
"sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8",
"sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe",
"sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf",
"sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e",
"sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741",
"sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09",
"sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd",
"sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034",
"sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420",
"sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c",
"sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab",
"sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba",
"sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e",
"sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609",
"sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2",
"sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49",
"sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b",
"sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d",
"sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce",
"sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9",
"sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4",
"sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773",
"sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723",
"sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c",
"sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f",
"sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1",
"sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260",
"sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a"
],
"index": "pypi",
"version": "==4.5.3"
},
"coveralls": {
"hashes": [
"sha256:d3d49234bffd41e91b241a69f0ebb9f64d7f0515711a76134d53d4647e7eb509",
"sha256:dafabcff87425fa2ab3122dee21229afbb4d6692cfdacc6bb895f7dfa8b2c849"
],
"index": "pypi",
"version": "==1.8.1"
},
"cryptography": {
"hashes": [
"sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c",
"sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643",
"sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216",
"sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799",
"sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a",
"sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9",
"sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc",
"sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8",
"sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53",
"sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1",
"sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609",
"sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292",
"sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e",
"sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6",
"sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed",
"sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"
],
"version": "==2.7"
},
"docopt": {
"hashes": [
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
],
"version": "==0.6.2"
},
"enum34": {
"hashes": [
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
],
"markers": "python_version < '3'",
"version": "==1.1.6"
},
"funcsigs": {
"hashes": [
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
],
"markers": "python_version < '3.0'",
"version": "==1.0.2"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"importlib-metadata": {
"hashes": [
"sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7",
"sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"
],
"version": "==0.18"
},
"ipaddress": {
"hashes": [
"sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794",
"sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c"
],
"version": "==1.0.22"
},
"more-itertools": {
"hashes": [
"sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4",
"sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc",
"sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"
],
"markers": "python_version <= '2.7'",
"version": "==5.0.0"
},
"packaging": {
"hashes": [
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
],
"version": "==19.0"
},
"pathlib2": {
"hashes": [
"sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e",
"sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8"
],
"markers": "python_version < '3.6'",
"version": "==2.3.4"
},
"pluggy": {
"hashes": [
"sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc",
"sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"
],
"version": "==0.12.0"
},
"py": {
"hashes": [
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
],
"version": "==1.8.0"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pyopenssl": {
"hashes": [
"sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200",
"sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6"
],
"version": "==19.0.0"
},
"pyparsing": {
"hashes": [
"sha256:530d8bf8cc93a34019d08142593cf4d78a05c890da8cf87ffa3120af53772238",
"sha256:f78e99616b6f1a4745c0580e170251ef1bbafc0d0513e270c4bd281bf29d2800"
],
"version": "==2.4.1"
},
"pytest": {
"hashes": [
"sha256:6aa9bc2f6f6504d7949e9df2a756739ca06e58ffda19b5e53c725f7b03fb4aae",
"sha256:b77ae6f2d1a760760902a7676887b665c086f71e3461c64ed2a312afcedc00d6"
],
"index": "pypi",
"version": "==4.6.4"
},
"requests": {
"hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
"version": "==2.22.0"
},
"scandir": {
"hashes": [
"sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e",
"sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022",
"sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f",
"sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f",
"sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae",
"sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173",
"sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4",
"sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32",
"sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188",
"sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d",
"sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"
],
"markers": "python_version < '3.5'",
"version": "==1.10.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"urllib3": {
"extras": [
"secure"
],
"hashes": [
"sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4",
"sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"
],
"markers": "python_version < '3'",
"version": "==1.24.3"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
],
"version": "==0.1.7"
},
"zipp": {
"hashes": [
"sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a",
"sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"
],
"version": "==0.5.2"
}
}
}

View file

@ -1,2 +1,6 @@
# genrss
[![Build Status](https://travis-ci.org/icetemple/genrss.svg?branch=master)](https://travis-ci.org/icetemple/genrss)
[![Coverage Status](https://coveralls.io/repos/github/icetemple/genrss/badge.svg?branch=master)](https://coveralls.io/github/icetemple/genrss?branch=master)
RSS generator for python

139
genrss/__init__.py Normal file
View file

@ -0,0 +1,139 @@
import mimetypes
from lxml.etree import Element, CDATA, tostring
from typing import Optional, List, NoReturn
from datetime import datetime
from collections import namedtuple
import pytz
Enclosure = namedtuple('Enclosure', ('url', 'size', 'type'))
Enclosure.__new__.__defaults__ = (None, None, None)
RSS_DEFAULT_GENERATOR = f'Generated by genrss for python'
def create_element(name: str, text=None, children=None, **kwargs) -> Element:
el = Element(name, **kwargs)
if text:
if isinstance(text, datetime):
text = text.replace(tzinfo=pytz.timezone('GMT')). \
strftime("%a, %d %b %Y %H:%M:%S %Z")
el.text = text
elif isinstance(children, (list, tuple)):
for child in children:
el.append(child)
return el
class GenRSS:
def __init__(self, title: str, site_url: str, feed_url: str, **kwargs):
self.title: str = title
self.site_url: str = site_url
self.feed_url: str = feed_url
self.description: str = kwargs.pop('description', self.title)
self.image_url: Optional[str] = kwargs.pop('image_url', None)
self.author: Optional[str] = kwargs.pop('author', None)
self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None)
self.copyright: Optional[str] = kwargs.pop('copyright', None)
self.language: Optional[str] = kwargs.pop('language', None)
self.editor: Optional[str] = kwargs.pop('editor', None)
self.webmaster: Optional[str] = kwargs.pop('webmaster', None)
self.docs_url: Optional[str] = kwargs.pop('docs_url', None)
self.categories: List[str] = kwargs.pop('categories', [])
self.items: List[Element] = []
self.generator = kwargs.pop('generator', RSS_DEFAULT_GENERATOR)
self.root_version = '2.0'
self.root_nsmap = {
'atom': 'http://www.w3.org/2005/Atom'
}
def item(self, title: str, **kwargs) -> NoReturn:
description: str = kwargs.pop('description', '')
url: Optional[str] = kwargs.pop('url', None)
guid: Optional[str] = kwargs.pop('guid', None)
author: Optional[str] = kwargs.pop('author', None)
categories: List[str] = kwargs.pop('categories', [])
enclosure: Optional[Enclosure] = kwargs.pop('enclosure', None)
pub_date: Optional[datetime] = kwargs.pop('pub_date', None)
item = create_element('item', children=[
create_element('title', CDATA(title)),
create_element('description', CDATA(description)),
])
if url:
item.append(create_element('link', url))
item.append(create_element(
'guid',
attrib={'isPermaLink': str(bool(not guid and url)).lower()},
text=(guid or url or CDATA(title))
))
if author or self.author:
if 'dc' not in self.root_nsmap:
self.root_nsmap['dc'] = 'http://purl.org/dc/elements/1.1/'
item.append(create_element(
'{http://purl.org/dc/elements/1.1/}creator',
CDATA(author or self.author)
))
for category in categories:
item.append(create_element('category', CDATA(category)))
if enclosure:
item.append(create_element(
'enclosure',
url=enclosure.url,
length=str(enclosure.size or 0),
type=enclosure.type or mimetypes.guess_type(enclosure.url)[0]
))
if pub_date:
item.append(create_element('pubDate', pub_date))
self.items.append(item)
def xml(self, pretty: bool = False) -> str:
root = Element('rss', nsmap=self.root_nsmap, version=self.root_version)
channel = create_element('channel', children=[
create_element('title', CDATA(self.title)),
create_element('description', CDATA(self.description)),
create_element('link', self.site_url),
create_element('{http://www.w3.org/2005/Atom}link',
href=self.feed_url, rel='self',
type='application/rss+xml'),
create_element('generator', self.generator),
create_element('lastBuildDate', datetime.utcnow())
])
if self.image_url:
channel.append(create_element('image', children=[
create_element('url', self.image_url),
create_element('title', CDATA(self.title)),
create_element('link', self.site_url)
]))
for category in self.categories:
channel.append(create_element('category', CDATA(category)))
if self.pub_date:
channel.append(create_element('pubDate', self.pub_date))
if self.copyright:
channel.append(create_element('copyright', CDATA(self.copyright)))
if self.language:
channel.append(create_element('language', CDATA(self.language)))
if self.editor:
channel.append(create_element('managingEditor', CDATA(self.editor)))
if self.webmaster:
channel.append(create_element('webMaster', CDATA(self.webmaster)))
if self.docs_url:
channel.append(create_element('docs', self.docs_url))
for item in self.items:
channel.append(item)
root.append(channel)
return '<?xml version="1.0" encoding="UTF-8"?>\n' \
+ tostring(root, pretty_print=pretty).decode('utf-8')

3
requirements-test.txt Normal file
View file

@ -0,0 +1,3 @@
coverage==4.5.3
coveralls==1.8.1
pytest==4.6.4

25
setup.py Normal file
View file

@ -0,0 +1,25 @@
from setuptools import setup
with open('README.md', 'r') as f:
readme = f.read()
if __name__ == '__main__':
setup(
name='genrss',
version='1.0.0',
author='Dmitriy Pleshevskiy',
author_email='dmitriy@ideascup.me',
description='RSS feed generator for python',
long_description=readme,
long_description_content_type='text/markdown',
package_data={'': ['LICENSE', 'README.md']},
include_package_data=True,
license='MIT',
packages=['genrss'],
install_requires=[
'lxml==4.3.4',
'pytz==2019.1'
]
)

10
tests/support.py Normal file
View file

@ -0,0 +1,10 @@
from genrss import GenRSS
def create_rss(**kwargs):
return GenRSS(title='SmartFridge', site_url='https://smartfridge.me/',
feed_url='https://smartfridge.me/rss.xml', **kwargs)
def create_item(feed, **kwargs):
feed.item(title='Recipe', **kwargs)

111
tests/test_rss.py Normal file
View file

@ -0,0 +1,111 @@
from datetime import datetime
from textwrap import dedent
import pytz
import pytest
from genrss import RSS_DEFAULT_GENERATOR
from tests.support import create_rss
def test_init_rss():
feed = create_rss()
xml = feed.xml()
assert xml
assert '<title><![CDATA[SmartFridge]]></title>' in xml
assert '<description><![CDATA[SmartFridge]]></description>' in xml
assert '<link>https://smartfridge.me/</link>' in xml
assert '<atom:link href="https://smartfridge.me/rss.xml" rel="self" ' \
'type="application/rss+xml"/>' in xml
assert '<generator>{}</generator>'.format(RSS_DEFAULT_GENERATOR) in xml
@pytest.mark.parametrize('description, expose', [
pytest.param('a' * 10, 'a' * 10, id='short(10)'),
pytest.param('a' * 285, 'a' * 285, id='long(285)'),
pytest.param(
dedent('''\
This is text with
new lines.'''),
dedent('''\
This is text with
new lines.'''),
id='+nl'
)
])
def test_feed_description(description, expose):
feed = create_rss(description=description)
xml = feed.xml()
assert xml
assert '<description><![CDATA[{}]]></description>'.format(expose) in xml
@pytest.mark.parametrize('copyright, expose', [
pytest.param('copyright © genrss', 'copyright &#169; genrss', id='copy'),
])
def test_feed_copyright(copyright, expose):
feed = create_rss(copyright=copyright)
xml = feed.xml()
assert xml
assert '<copyright><![CDATA[{}]]></copyright>'.format(expose) in xml
def test_feed_pub_date():
pub_date = datetime.utcnow()
feed = create_rss(pub_date=pub_date)
xml = feed.xml()
expose = pub_date.replace(tzinfo=pytz.timezone('GMT')). \
strftime("%a, %d %b %Y %H:%M:%S %Z")
assert xml
assert '<pubDate>{}</pubDate>'.format(expose) in xml
def test_feed_language():
lang = 'en'
feed = create_rss(language=lang)
xml = feed.xml()
assert xml
assert '<language><![CDATA[{}]]></language>'.format(lang) in xml
def test_feed_editor():
editor = 'Dmitriy Pleshevskiy'
feed = create_rss(editor=editor)
xml = feed.xml()
assert xml
assert ('<managingEditor><![CDATA[{}]]>'
'</managingEditor>').format(editor) in xml
def test_feed_image_url():
image_url = 'https://s3.smartfridge.me/image.jpg'
feed = create_rss(image_url=image_url)
xml = feed.xml()
assert xml
assert (f'<image><url>{image_url}</url>'
'<title><![CDATA[SmartFridge]]></title>'
'<link>https://smartfridge.me/</link></image>') in xml
def test_feed_webmaster():
webmaster = 'Dmitriy Pleshevskiy'
feed = create_rss(webmaster=webmaster)
xml = feed.xml()
assert xml
assert '<webMaster><![CDATA[{}]]></webMaster>'.format(webmaster) in xml
def test_feed_docs_url():
docs_url = 'https://smartfridge.me/docs'
feed = create_rss(docs_url=docs_url)
xml = feed.xml()
assert xml
assert '<docs>{}</docs>'.format(docs_url) in xml
def test_feed_categories():
categories = ['Category 1', 'Category 2']
feed = create_rss(categories=categories)
xml = feed.xml()
assert xml
assert '<category><![CDATA[Category 1]]></category>' \
'<category><![CDATA[Category 2]]></category>' in xml

96
tests/test_rss_item.py Normal file
View file

@ -0,0 +1,96 @@
from uuid import uuid4
from datetime import datetime
import pytz
import pytest
from genrss import Enclosure
from tests.support import create_rss, create_item
@pytest.fixture()
def feed():
return create_rss()
def test_item(feed):
create_item(feed)
xml = feed.xml()
assert xml
assert '<item><title><![CDATA[Recipe]]></title>' \
'<description><![CDATA[]]></description>' \
'<guid isPermaLink="false"><![CDATA[Recipe]]></guid>' \
'</item>' in xml
def test_item_description(feed):
description = 'description'
create_item(feed, description=description)
xml = feed.xml()
assert xml
assert '<item><title><![CDATA[Recipe]]></title>' \
'<description><![CDATA[{}]]></description>' \
'<guid isPermaLink="false"><![CDATA[Recipe]]></guid>' \
'</item>'.format(description) in xml
def test_item_guid(feed):
guid = uuid4().hex
create_item(feed, guid=guid)
xml = feed.xml()
assert xml
assert '<item><title><![CDATA[Recipe]]></title>' \
'<description><![CDATA[]]></description>' \
'<guid isPermaLink="false">{}</guid>' \
'</item>'.format(guid) in xml
def test_item_url(feed):
url = 'https://smartfridge.me/'
create_item(feed, url=url)
xml = feed.xml()
assert xml
assert '<item><title><![CDATA[Recipe]]></title>' \
'<description><![CDATA[]]></description>' \
'<link>{url}</link>' \
'<guid isPermaLink="true">{url}</guid>' \
'</item>'.format(url=url) in xml
def test_item_author(feed):
author = 'Dmitriy Pleshevskiy'
create_item(feed, author=author)
create_item(feed, author=author)
xml = feed.xml()
assert xml
assert '<dc:creator><![CDATA[{}]]></dc:creator>' \
'</item>'.format(author) in xml
def test_item_categories(feed):
categories = ['Category 1', 'Category 2']
create_item(feed, categories=categories)
xml = feed.xml()
assert xml
assert '<category><![CDATA[Category 1]]></category>' \
'<category><![CDATA[Category 2]]></category>' \
'</item>' in xml
def test_item_pub_date(feed):
pub_date = datetime.utcnow()
expose = pub_date.replace(tzinfo=pytz.timezone('GMT')). \
strftime("%a, %d %b %Y %H:%M:%S %Z")
create_item(feed, pub_date=pub_date)
xml = feed.xml()
assert xml
assert '<pubDate>{}</pubDate>' \
'</item>'.format(expose) in xml
def test_item_enclosure(feed):
enclosure=Enclosure('https://smartfridge.me/image.jpg')
create_item(feed, enclosure=enclosure)
xml = feed.xml()
assert xml
assert '<enclosure length="{}" type="{}" url="{}"/>' \
'</item>'.format(0, 'image/jpeg', enclosure.url) in xml