diff --git a/.gitignore b/.gitignore index 3a24b70..fb2ec2a 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,5 @@ dmypy.json conda/ node_modules src/pxy.py +urls.txt +data.csv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..731f078 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.1 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.1 + hooks: + - id: ruff-format + +- repo: local + hooks: + - id: pr-mypy + name: mypy + entry: poetry run mypy + language: python + types: [python] + args: ["--ignore-missing-imports", "--scripts-are-modules"] + require_serial: true + \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index f7d1e12..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,237 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "b3dd2d5529f43e4c1fdd47c9ef3eb1256181c47775497ea0992b64baef238778" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "atomicwrites": { - "hashes": [ - "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", - "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" - ], - "version": "==1.4.0" - }, - "attrs": { - "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" - ], - "version": "==20.3.0" - }, - "certifi": { - "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" - ], - "version": "==2020.12.5" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" - }, - "importlib-metadata": { - "hashes": [ - "sha256:19192b88d959336bfa6bdaaaef99aeafec179eca19c47c804e555703ee5f07ef", - "sha256:2e881981c9748d7282b374b68e759c87745c25427b67ecf0cc67fb6637a1bff9" - ], - "markers": "python_version < '3.8'", - "version": "==4.0.0" - }, - "more-itertools": { - "hashes": [ - "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", - "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" - ], - "version": "==8.7.0" - }, - "numpy": { - "hashes": [ - "sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727", - "sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6", - "sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98", - "sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7", - "sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d", - "sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2", - "sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9", - "sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935", - "sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff", - "sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee", - "sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb", - "sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042", - "sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3", - "sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5", - "sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6", - "sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f", - "sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4", - "sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737", - "sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931", - "sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6", - "sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677", - "sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576", - "sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935", - "sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd" - ], - "version": "==1.20.2" - }, - "packaging": { - "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" - ], - "version": "==20.9" - }, - "pandas": { - "hashes": [ - "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b", - "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa", - "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846", - "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822", - "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167", - "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794", - "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204", - "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2", - "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2", - "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248", - "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8", - "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8", - "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296", - "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5", - "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d", - "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5", - "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0", - "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3", - "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb", - "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1" - ], - "index": "pypi", - "version": "==0.24.2" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "version": "==0.13.1" - }, - "py": { - "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" - ], - "index": "pypi", - "version": "==1.10.0" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "version": "==2.4.7" - }, - "pytest": { - "hashes": [ - "sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", - "sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77" - ], - "index": "pypi", - "version": "==5.0.1" - }, - "python-dateutil": { - "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" - ], - "version": "==2.8.1" - }, - "pytz": { - "hashes": [ - "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", - "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" - ], - "version": "==2021.1" - }, - "requests": { - "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" - ], - "index": "pypi", - "version": "==2.22.0" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "version": "==1.15.0" - }, - "sqlalchemy": { - "hashes": [ - "sha256:c30925d60af95443458ebd7525daf791f55762b106049ae71e18f8dd58084c2f" - ], - "index": "pypi", - "version": "==1.3.5" - }, - "tehran-stocks": { - "hashes": [ - "sha256:3d23bfedf3eefa49f07e98ee4b87ba0a3f90f1b89bef92553c372b44a921dedf", - "sha256:77edd64f8d2c7bec3d49897c4c184ff6c2837ff8827a20ee04124abc0afeb1e3" - ], - "index": "pypi", - "version": "==0.0.3" - }, - "typing-extensions": { - "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" - ], - "markers": "python_version < '3.8'", - "version": "==3.7.4.3" - }, - "urllib3": { - "hashes": [ - "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", - "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" - ], - "version": "==1.25.11" - }, - "wcwidth": { - "hashes": [ - "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", - "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" - ], - "version": "==0.2.5" - }, - "zipp": { - "hashes": [ - "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", - "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" - ], - "version": "==3.4.1" - } - }, - "develop": {} -} diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..6f1ae4d --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +exclude = tests \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index e8cb394..2bbc753 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiodns" @@ -136,6 +136,20 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "appnope" version = "0.1.3" @@ -414,6 +428,73 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.3.2" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "decorator" version = "5.1.1" @@ -1061,6 +1142,63 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "mypy" +version = "1.6.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, + {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, + {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, + {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, + {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, + {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, + {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, + {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, + {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, + {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, + {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, + {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, + {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, + {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, + {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, + {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, + {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, + {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "nodeenv" version = "1.8.0" @@ -1194,8 +1332,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -1429,6 +1567,143 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pydantic" +version = "2.4.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pygments" version = "2.16.0" @@ -1507,6 +1782,41 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.12.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, + {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1544,6 +1854,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1551,8 +1862,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1569,6 +1887,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1576,6 +1895,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1602,6 +1922,32 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.1.1" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b7cdc893aef23ccc14c54bd79a8109a82a2c527e11d030b62201d86f6c2b81c5"}, + {file = "ruff-0.1.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:620d4b34302538dbd8bbbe8fdb8e8f98d72d29bd47e972e2b59ce6c1e8862257"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a909d3930afdbc2e9fd893b0034479e90e7981791879aab50ce3d9f55205bd6"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3305d1cb4eb8ff6d3e63a48d1659d20aab43b49fe987b3ca4900528342367145"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c34ae501d0ec71acf19ee5d4d889e379863dcc4b796bf8ce2934a9357dc31db7"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6aa7e63c3852cf8fe62698aef31e563e97143a4b801b57f920012d0e07049a8d"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68367d1379a6b47e61bc9de144a47bcdb1aad7903bbf256e4c3d31f11a87ae"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc11955f6ce3398d2afe81ad7e49d0ebf0a581d8bcb27b8c300281737735e3a3"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd8eead88ea83a250499074e2a8e9d80975f0b324b1e2e679e4594da318c25"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f4780e2bb52f3863a565ec3f699319d3493b83ff95ebbb4993e59c62aaf6e75e"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8f5b24daddf35b6c207619301170cae5d2699955829cda77b6ce1e5fc69340df"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d3f9ac658ba29e07b95c80fa742b059a55aefffa8b1e078bc3c08768bdd4b11a"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3521bf910104bf781e6753282282acc145cbe3eff79a1ce6b920404cd756075a"}, + {file = "ruff-0.1.1-py3-none-win32.whl", hash = "sha256:ba3208543ab91d3e4032db2652dcb6c22a25787b85b8dc3aeff084afdc612e5c"}, + {file = "ruff-0.1.1-py3-none-win_amd64.whl", hash = "sha256:3ff3006c97d9dc396b87fb46bb65818e614ad0181f059322df82bbfe6944e264"}, + {file = "ruff-0.1.1-py3-none-win_arm64.whl", hash = "sha256:e140bd717c49164c8feb4f65c644046fe929c46f42493672853e3213d7bdbce2"}, + {file = "ruff-0.1.1.tar.gz", hash = "sha256:c90461ae4abec261609e5ea436de4a4b5f2822921cf04c16d2cc9327182dbbcc"}, +] + [[package]] name = "setuptools" version = "68.0.0" @@ -1640,6 +1986,7 @@ files = [ {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca46de16650d143a928d10842939dab208e8d8c3a9a8757600cae9b7c579c5cd"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, @@ -1649,26 +1996,35 @@ files = [ {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f23755c384c2969ca2f7667a83f7c5648fcf8b62a3f2bbd883d805454964a800"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8396e896e08e37032e87e7fbf4a15f431aa878c286dc7f79e616c2feacdb366c"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66da9627cfcc43bbdebd47bfe0145bb662041472393c03b7802253993b6b7c90"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-win32.whl", hash = "sha256:9a06e046ffeb8a484279e54bda0a5abfd9675f594a2e38ef3133d7e4d75b6214"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-win_amd64.whl", hash = "sha256:7cf8b90ad84ad3a45098b1c9f56f2b161601e4670827d6b892ea0e884569bd1d"}, {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc22807a7e161c0d8f3da34018ab7c97ef6223578fcdd99b1d3e7ed1100a5db"}, {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393cd06c3b00b57f5421e2133e088df9cabcececcea180327e43b937b5a7caa5"}, {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ab792ca493891d7a45a077e35b418f68435efb3e1706cb8155e20e86a9013c"}, {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:738d7321212941ab19ba2acf02a68b8ee64987b248ffa2101630e8fccb549e0d"}, {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, @@ -1677,7 +2033,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"} +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} [package.extras] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] @@ -1730,6 +2086,26 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "5.9.0" @@ -1745,6 +2121,31 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] +[[package]] +name = "types-pyyaml" +version = "6.0.12.12" +description = "Typing stubs for PyYAML" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, + {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.10" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "types-requests-2.31.0.10.tar.gz", hash = "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92"}, + {file = "types_requests-2.31.0.10-py3-none-any.whl", hash = "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc"}, +] + +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" version = "4.7.1" @@ -1989,4 +2390,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "24946359cbafbe855e41a09c6e1a13757c8a0104a7ca2cb718517d3c736d1ff2" +content-hash = "938655ad3859591da769e7c4c31a2d2063e774abeb827c8d3704e93be57b1d62" diff --git a/pyproject.toml b/pyproject.toml index bddd14a..d0ad8e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,13 @@ [tool.poetry] name = "tehran-stocks" -version = "2.0.0" +version = "3.0.0a0" description = "Data Downloader for Tehran stock market" authors = ["Mehdi Ghodsizadeh "] license = "MIT" readme = "README.md" homepage = "https://ghodsizadeh.github.io/tehran-stocks/" repository = "https://github.com/ghodsizadeh/tehran-stocks.git" -packages = [ - { include = "tehran_stocks", from = 'src' } -] + [tool.poetry.dependencies] @@ -22,6 +20,8 @@ aiohttp = "^3.8.5" aiodns = "^3.0.0" PyYAML = "^6.0" pandas = "^2.0.3" +pydantic = "^2.4.2" +tqdm = "^4.66.1" [tool.poetry.group.dev.dependencies] @@ -31,6 +31,12 @@ pytest = "^7.4.0" ipython = ">=7.31,<9.0" ipdb = "^0.13.9" pytest-asyncio = "^0.21.1" +ruff = "^0.1.1" +mypy = "^1.6.1" +types-pyyaml = "^6.0.12.12" +types-requests = "^2.31.0.10" +pytest-mock = "^3.12.0" +pytest-cov = "^4.1.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..04ab379 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + online + offline \ No newline at end of file diff --git a/setup.py b/setup.py index 0fdae93..2a0d8c5 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import setup # read the contents of your README file @@ -9,7 +9,6 @@ LONGDESC = f.read() setup( - name="tehran-stocks", version="0.8.2.2", description="Data Downloader for Tehran stock market", diff --git a/src/tehran_stocks/__init__.py b/src/tehran_stocks/__init__.py deleted file mode 100644 index 7f184ab..0000000 --- a/src/tehran_stocks/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -import os - -import tehran_stocks.config as db -from tehran_stocks.download import ( - get_all_price, - get_stock_detail, - get_stock_groups, - get_stock_ids, - update_group, - update_stock_price, -) -from tehran_stocks.models import StockPrice, Stocks, get_asset - -from .initializer import init_db, fill_db - - -def db_is_empty(): - try: - db.session.execute("select * from stocks limit 1;") - return False - except: - return True - - -if db_is_empty(): - print("No database founded.") - init_db() - fill_db() diff --git a/src/tehran_stocks/config/__init__.py b/src/tehran_stocks/config/__init__.py deleted file mode 100644 index 8e34de2..0000000 --- a/src/tehran_stocks/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .engine import * diff --git a/src/tehran_stocks/config/engine.py b/src/tehran_stocks/config/engine.py deleted file mode 100644 index e62ba6b..0000000 --- a/src/tehran_stocks/config/engine.py +++ /dev/null @@ -1,91 +0,0 @@ -import contextlib -from sqlalchemy import Column, Float, Integer, String, create_engine -from sqlalchemy.ext.declarative import declarative_base, declared_attr -from sqlalchemy.orm import sessionmaker -from sqlalchemy import * -from pathlib import Path -import os -import sqlite3 -import logging -import yaml - - -HOME_PATH = str(Path.home()) -TSE_FOLDER = ".tse" -CONFIG_PATH = f"{os.path.join(HOME_PATH, TSE_FOLDER)}/config.yml" - -def create_config(): - # create config.yml from config.deafult.yml - if not os.path.exists(CONFIG_PATH): - with open(os.path.join(os.path.dirname(__file__), "config.default.yml"), "r") as f: - config = yaml.full_load(f) - with contextlib.suppress(FileExistsError): - path = os.path.join(HOME_PATH, TSE_FOLDER) - os.mkdir(path) - with open(CONFIG_PATH, "w") as f: - - - yaml.dump(config, f) - -create_config() - - - -with open(CONFIG_PATH, "r") as f: - config = yaml.full_load(f) - -defualt_db_path = os.path.join(f"{HOME_PATH}/{TSE_FOLDER}/stocks.db") - -db_path = config.get("database").get("path") -if db_path is None: - db_path = defualt_db_path - -default_engine_URI = f"sqlite:///{db_path}" -engine = config.get("database").get("engine") -if engine == "sqlite": - engine_URI = default_engine_URI -else: - engine = config.get("database").get("engine") - host = config.get("database").get("host") - port = config.get("database").get("port") - database = config.get("database").get("database") - user = config.get("database").get("user") - password = config.get("database").get("password") - engine_URI = f"{engine}://{user}:{password}@{host}:{port}/{database}" - - -logging.info(engine_URI) -engine = create_engine(engine_URI) - - -Session = sessionmaker() -Session.configure(bind=engine) -session = Session() - - -class ClassProperty(object): - def __init__(self, fget): - self.fget = fget - - def __get__(self, owner_self, owner_cls): - return self.fget(owner_cls) - - def __repr__(self): - return None - - -class QueryMixin: - @ClassProperty - def query(cls): - return session.query(cls) - - def display(self): - data = self.__dict__ - try: - del data["_sa_instance_state"] - except: - pass - return data - - -Base = declarative_base(cls=QueryMixin) diff --git a/src/tehran_stocks/download/__init__.py b/src/tehran_stocks/download/__init__.py deleted file mode 100644 index b44e837..0000000 --- a/src/tehran_stocks/download/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .names import fill_stock_table, get_stock_detail, get_stock_groups, get_stock_ids -from .price import get_all_price, update_group, update_stock_price diff --git a/src/tehran_stocks/download/base.py b/src/tehran_stocks/download/base.py deleted file mode 100644 index 83a2f0b..0000000 --- a/src/tehran_stocks/download/base.py +++ /dev/null @@ -1 +0,0 @@ -BASE_URL = "http://old.tsetmc.com" diff --git a/src/tehran_stocks/download/price.py b/src/tehran_stocks/download/price.py deleted file mode 100644 index 3c3f1e9..0000000 --- a/src/tehran_stocks/download/price.py +++ /dev/null @@ -1,155 +0,0 @@ -import re -import time -from jdatetime import date as jdate -from datetime import datetime -import asyncio -import aiohttp -import pandas as pd -import requests -import io - -import tehran_stocks.config as db -from tehran_stocks.models import StockPrice, Stocks -from .base import BASE_URL - - -def convert_to_shamsi(date): - date = str(date) - return jdate.fromgregorian( - day=int(date[-2:]), month=int(date[4:6]), year=int(date[:4]) - ).strftime("%Y/%m/%d") - - -def get_stock_price_history(stock_id: int) -> pd.DataFrame: - """Get stock price history from the web. - - params: - ---------------- - stock_id: int - http://www.old.tsetmc.com/Loader.aspx?ParTree=151311&i=**35700344742885862#** - number after i= - - return: - ---------------- - pd.DataFrame - date: str - open: float - high: float - low: float - close: float - volume: int - - example - ---------------- - df = get_stock_price_history(35700344742885862) - """ - now = datetime.now().strftime("%Y%m%d") - url = f"{BASE_URL}/tse/data/Export-txt.aspx?a=InsTrade&InsCode={stock_id}&DateFrom=20000101&DateTo={now}&b=0" - - s = requests.get(url).content - df = pd.read_csv(io.StringIO(s.decode("utf-8"))) - df.columns = [i[1:-1].lower() for i in df.columns] - if 'ticker' not in df.columns or 'close' not in df.columns: - return pd.DataFrame() - return df - - -async def update_stock_price(code: str): - """ - Update (or download for the first time) Stock prices - - - params: - ---------------- - code: str or intege - - example - ---------------- - `update_stock_price('44891482026867833') #Done` - or use inside Stock object - ``` - from tehran_stocks.models import Stocks - stock = Stocks.query.first() - stock.update() #Done - """ - try: - now = datetime.now().strftime("%Y%m%d") - try: - max_date_query = ( - f"select max(dtyyyymmdd) as date from stock_price where code = '{code}'" - ) - max_date = pd.read_sql(max_date_query, db.engine) - last_date = max_date.date.iat[0] - except Exception as e: - last_date = None - try: - if last_date is None: # no any record added in database - url = f"{BASE_URL}/tse/data/Export-txt.aspx?a=InsTrade&InsCode={code}&DateFrom=20000101&DateTo={now}&b=0" - elif str(last_date) < now: # need to updata new price data - url = f"{BASE_URL}/tse/data/Export-txt.aspx?a=InsTrade&InsCode={code}&DateFrom={str(last_date)}&DateTo={now}&b=0" - else: # The price data for this code is updateed - return - except Exception as e: - print(f"Error on formating price:{str(e)}") - - async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: - data = await resp.text() - df = pd.read_csv(io.StringIO(data)) - df.columns = [i[1:-1].lower() for i in df.columns] - df["code"] = code - df["date_shamsi"] = df["dtyyyymmdd"].apply(convert_to_shamsi) - try: - q = f"select dtyyyymmdd as date from stock_price where code = '{code}'" - temp = pd.read_sql(q, db.engine) - df = df[~df.dtyyyymmdd.isin(temp.date)] - except: - pass - df.to_sql("stock_price", db.engine, if_exists="append", index=False) - return True, code - - except Exception as e: - return e, code - - -def update_group(code): - """ - Update and download data of all stocks in a group.\n - - `Warning: Stock table should be updated` - """ - stocks = db.session.query(Stocks.code).filter_by(group_code=code).all() - print("updating group", code) - loop = asyncio.get_event_loop() - tasks = [update_stock_price(stock[0]) for stock in stocks] - try: - results = loop.run_until_complete(asyncio.gather(*tasks)) - except RuntimeError: - WARNING_COLOR = "\033[93m" - ENDING_COLOR = "\033[0m" - print(WARNING_COLOR, "Please update stock table", ENDING_COLOR) - print( - f"{WARNING_COLOR}If you are using jupyter notebook, please run following command:{ENDING_COLOR}" - ) - print("```") - print("%pip install nest_asyncio") - print("import nest_asyncio; nest_asyncio.apply()") - print("from tehran_stocks.download import get_all_price") - print("get_all_price()") - print("```") - raise RuntimeError - - print("group", code, "updated") - return results - - -def get_all_price(): - codes = db.session.query(db.distinct(Stocks.group_code)).all() - for i, code in enumerate(codes): - print( - f" total progress: {100*(i+1)/len(codes):.2f}%", - end="\r", - ) - update_group(code[0]) - - print("Download Finished.") diff --git a/src/tehran_stocks/initializer.py b/src/tehran_stocks/initializer.py deleted file mode 100644 index e97ca2f..0000000 --- a/src/tehran_stocks/initializer.py +++ /dev/null @@ -1,42 +0,0 @@ -import os - -from tehran_stocks import db, models, update_group, get_all_price -from tehran_stocks.download import fill_stock_table - - -def init_db(): - print("creating database") - path = os.path.join(db.HOME_PATH, db.TSE_FOLDER) - - - try: - os.mkdir(path) - print("making package folder...") - print("Includes: config.yml and stocks.db if you are using sqlite.") - print("you can change config.yml to your needs.") - except FileExistsError: - print("folder already exists") - models.create() - print(f"DataBase created in: {path}") - - -def fill_db(): - print("downloading stock name and details from TSETMC") - print("may take few minutes") - - fill_stock_table() - print("Stock table is available now, example:") - print("from tehran_stocks import Stocks") - print('stock =Stocks.query.filter_by(name="کگل").first()') - - a = input("Do you want to download all price? [y,(n)]") - if a == "y": - print("Downloading price:") - get_all_price() - else: - print("if you want download all prices use tehran_stocks.get_all_price() ") - print("if you want download price history of a specfic stock use: ") - print("stock.update()") - print("or use tehran_stocks.update_group(id) ") - print("For more info go to:") - print("https://github.com/ghodsizadeh/tehran-stocks") diff --git a/src/tehran_stocks/models/__init__.py b/src/tehran_stocks/models/__init__.py deleted file mode 100644 index 8c21900..0000000 --- a/src/tehran_stocks/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .create import create -from .stocks import StockPrice, Stocks, get_asset diff --git a/src/tehran_stocks/models/create.py b/src/tehran_stocks/models/create.py deleted file mode 100644 index af3ef14..0000000 --- a/src/tehran_stocks/models/create.py +++ /dev/null @@ -1,7 +0,0 @@ -import tehran_stocks.config as db -from .stocks import StockPrice, Stocks - - -def create(): - Stocks.__table__.create(db.engine) - StockPrice.__table__.create(db.engine) diff --git a/src/tehran_stocks/models/stocks.py b/src/tehran_stocks/models/stocks.py deleted file mode 100644 index ce994ed..0000000 --- a/src/tehran_stocks/models/stocks.py +++ /dev/null @@ -1,209 +0,0 @@ -from tehran_stocks.config import * -from sqlalchemy.orm import relationship -import pandas as pd -import requests -import jalali_pandas -from tehran_stocks.download.base import BASE_URL - - -class Stocks(Base): - __tablename__ = "stocks" - - id = Column(Integer, primary_key=True) - name = Column(String) - title = Column(String) - group_name = Column(String) - group_code = Column(Integer) - instId = Column(String) - insCode = Column(String) - code = Column(String, unique=True) - sectorPe = Column(Float) - shareCount = Column(Float) - estimatedEps = Column(Float) - baseVol = Column(Float) - prices = relationship("StockPrice", backref="stock") - _cached = False - _dfcounter = 0 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - @property - def df(self) -> pd.DataFrame: - """dataframe of stock price with date and OHLC""" - self._dfcounter += 1 - if self._cached: - return self._df - query = f"select * from stock_price where code = '{self.code}'" - df = pd.read_sql(query, engine) - if df.empty: - self._cached = True - self._df = df - return self._df - df["date"] = pd.to_datetime(df["dtyyyymmdd"], format="%Y%m%d") - df["jdate"] = df.date.jalali.to_jalali() - df = df.sort_values("date") - df.reset_index(drop=True, inplace=True) - df.set_index("date", inplace=True) - self._cached = True - self._df = df - - return self._df - - def get_dividend(self) -> pd.DataFrame: - """get changes in price for dividend and changes in share - postive value is dividend and negative value is changes in share - - Returns: - pd.DataFrame: _description_ - """ - - url = f"{BASE_URL}/Loader.aspx?Partree=15131G&i={self.code}" - r = requests.get(url) - changes = pd.read_html(r.text)[0] - changes.columns = ["date", "after", "before"] - changes["dividend"] = changes.before - changes.after - changes["date"] = changes.date.jalali.parse_jalali("%Y/%m/%d") - changes["gdate"] = changes.date.jalali.to_gregorian() - return changes - - def get_shares_history(self) -> pd.DataFrame: - """_summary_get changes in shares - - Returns: - pd.DataFrame: return day of shares changes and shares count [date, new_shares, old_shares, gdate] - """ - url = f"{BASE_URL}/Loader.aspx?Partree=15131H&i={self.code}" - r = requests.get(url) - df = pd.read_html(r.text)[0] - df.columns = ["date", "new_shares", "old_shares"] - df["date"] = df.date.jalali.parse_jalali("%Y/%m/%d") - df["gdate"] = df.date.jalali.to_gregorian() - return df - - @property - def mpl(self): - self._mpl = self.df.rename( - columns={ - "close": "Close", - "open": "Open", - "high": "High", - "low": "Low", - "vol": "Volume", - } - ) - return self._mpl - - def update(self): - from tehran_stocks.download import update_stock_price - - try: - return update_stock_price(self.code) - except: - return False - - def summary(self): - """summart of stock""" - df = self.df - sdate = df.index.min().strftime("%Y/%m/%d") - edate = df.index.max().strftime("%Y/%m/%d") - - print(f"Start date: {sdate}") - print(f"End date: {edate}") - print(f"Total days: {len(df)}") - - def get_instant_detail(self) -> dict: - """get instant detail of stock - last_price, last_close, last_open, last_high, last_low, last_vol, trade_count, trade_value,market_cap - instantly from the website - - Returns: - dict: { last_price, last_close, last_open, last_high, last_low, last_vol, trade_count, trade_value,market_cap} - """ - url = ( - f"{BASE_URL}/tsev2/data/instinfodata.aspx?i={self.code}&c=27%20" - ) - headers = { - "Connection": "keep-alive", - "Accept": "text/plain, */*; q=0.01", - "DNT": "1", - "X-Requested-With": "XMLHttpRequest", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36", - } - # 12:29:40,A ,4300,4364,4577,4361,4577,4250,195,702025,3070743824,0,20220326,122940; - r = requests.get(url, headers=headers) - main_response = r.text.split(";")[0] - main_response = main_response.split(",") - - self.time = main_response[0] - self.last_price = main_response[2] - self.last_close = main_response[3] - self.last_high = main_response[4] - self.last_low = main_response[5] - self.last_open = main_response[6] - self.trade_count = main_response[7] - self.trade_volume = main_response[8] - self.trade_value = main_response[9] - self.market_cap = main_response[10] - self.date_string = main_response[12] - self.time_string = main_response[13] - del main_response[11] - del main_response[1] - - keys = [ - "time", - "last_price", - "last_close", - "last_high", - "last_low", - "last_open", - "trade_count", - "trade_volume", - "trade_value", - "market_cap", - "date_string", - "time_string", - ] - return dict(zip(keys, main_response)) - - def __repr__(self): - return f"{self.title}-{self.name}-{self.group_name}" - - def __str__(self): - return self.name - - @staticmethod - def get_group(): - return ( - session.query(Stocks.group_code, Stocks.group_name) - .group_by(Stocks.group_code) - .all() - ) - - -class StockPrice(Base): - __tablename__ = "stock_price" - - id = Column(Integer, primary_key=True) - code = Column(String, ForeignKey("stocks.code"), index=True) - ticker = Column(String) - date = Column("dtyyyymmdd", Integer, index=True) - date_shamsi = Column(String) - first = Column(Float) - high = Column(Float) - low = Column(Float) - close = Column(Float) - value = Column(BIGINT) - vol = Column(BIGINT) - openint = Column(Integer) - per = Column(String) - open = Column(Float) - last = Column(Float) - - def __repr__(self): - return f"{self.stock.name}, {self.date}, {self.close:.0f}" - - -def get_asset(name): - name = name.replace("ی", "ي").replace("ک", "ك") - return Stocks.query.filter_by(name=name).first() diff --git a/tehran_stocks/__init__.py b/tehran_stocks/__init__.py new file mode 100644 index 0000000..ea1710a --- /dev/null +++ b/tehran_stocks/__init__.py @@ -0,0 +1,36 @@ +import tehran_stocks.config as db +from tehran_stocks.download import ( + get_stock_detail, + get_stock_groups, +) +from tehran_stocks.models import InstrumentPrice, Instrument + +# from .initializer import init_db + + +__all__ = [ + "get_stock_detail", + "get_stock_groups", + "get_all_price", + "update_group", + "Instrument", + "InstrumentPrice", +] + + +def db_is_empty(): + try: + db.session.execute("select * from stocks limit 1;") + return False + except Exception: + return True + + # init_db() + # fill_db() + + +# if __name__ == "__main__": +# engine = create_engine_with_config() +# engine = None +# Session = sessionmaker(bind=engine) +# session = Session() diff --git a/tehran_stocks/config/__init__.py b/tehran_stocks/config/__init__.py new file mode 100644 index 0000000..99cd92b --- /dev/null +++ b/tehran_stocks/config/__init__.py @@ -0,0 +1,3 @@ +from .engine import Base, QueryMixin + +__all__ = ["Base", "QueryMixin"] diff --git a/src/tehran_stocks/config/config.default.yml b/tehran_stocks/config/config.default.yml similarity index 79% rename from src/tehran_stocks/config/config.default.yml rename to tehran_stocks/config/config.default.yml index a12d4cd..683e8cf 100644 --- a/src/tehran_stocks/config/config.default.yml +++ b/tehran_stocks/config/config.default.yml @@ -4,4 +4,4 @@ database: port: None user: None password: None - database: None + database: tse_data diff --git a/tehran_stocks/config/config_file.py b/tehran_stocks/config/config_file.py new file mode 100644 index 0000000..296429f --- /dev/null +++ b/tehran_stocks/config/config_file.py @@ -0,0 +1,42 @@ +import contextlib +import os +from pathlib import Path +from typing import Dict, Optional + +import yaml + +HOME_PATH = str(Path.home()) +TSE_FOLDER = ".tse" +CONFIG_PATH = f"{os.path.join(HOME_PATH, TSE_FOLDER)}/config.yml" + + +def create_tse_folder() -> bool: + # Create the .tse folder if it doesn't exist + with contextlib.suppress(FileExistsError): + path = os.path.join(HOME_PATH, TSE_FOLDER) + os.mkdir(path) + return True + return False + + +def create_config(): + # Create config.yml from config.default.yml + if not os.path.exists(CONFIG_PATH): + print("creating ~/.tse/config.yml") + with open( + os.path.join(os.path.dirname(__file__), "config.default.yml"), "r" + ) as f: + config = yaml.full_load(f) + with open(CONFIG_PATH, "w") as f: + yaml.dump(config, f) + print("config.yml created") + + +def get_database_config() -> Dict[str, Optional[str]]: + # Get the database configuration from the config file + with open(CONFIG_PATH, "r") as f: + config = yaml.full_load(f) + if config is None: + os.remove(CONFIG_PATH) + print("config.yml was empty, please try again") + return config.get("database", {}) diff --git a/tehran_stocks/config/engine.py b/tehran_stocks/config/engine.py new file mode 100644 index 0000000..84a4d1f --- /dev/null +++ b/tehran_stocks/config/engine.py @@ -0,0 +1,72 @@ +import logging +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from .config_file import ( + HOME_PATH, + TSE_FOLDER, + get_database_config, +) + + +def create_engine_uri(database_config): + """Create the database URI from the configuration""" + engine = database_config.get("engine", "sqlite") + database = database_config.get("database", "tse_data") + + if engine == "sqlite": + path = database_config.get("path", f"{HOME_PATH}/{TSE_FOLDER}/{database}.db") + return f"sqlite:///{path}" + host = database_config.get("host", "localhost") + port = database_config.get("port", "5432") + user = database_config.get("user", "tse") + password = database_config.get("password", "tse") + return f"{engine}://{user}:{password}@{host}:{port}/{database}" + + +def get_engine_from_config(): + database_config = get_database_config() + engine_uri = create_engine_uri(database_config) + logging.info(engine_uri) + engine = create_engine(engine_uri) + return engine + + +class ClassProperty(object): + def __init__(self, fget): + self.fget = fget + + def __get__(self, owner_self, owner_cls): + return self.fget(owner_cls) + + def __repr__(self): + return None + + +def get_session(engine=None): + from sqlalchemy.orm import sessionmaker + + if engine is None: + engine = get_engine_from_config() + session_maker = sessionmaker(bind=engine) + session = session_maker() + return session + + +class QueryMixin: + @ClassProperty + def query(cls): + engine = get_engine_from_config() + session = get_session(engine) + + return session.query(cls) + + def display(self): + data = self.__dict__ + try: + del data["_sa_instance_state"] + except AttributeError: + pass + return data + + +Base = declarative_base(cls=QueryMixin) diff --git a/tehran_stocks/data/__init__.py b/tehran_stocks/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tehran_stocks/data/groups.py b/tehran_stocks/data/groups.py new file mode 100644 index 0000000..a59bdde --- /dev/null +++ b/tehran_stocks/data/groups.py @@ -0,0 +1,76 @@ +from enum import Enum, unique + + +@unique +class IndustryGroup(Enum): + def __init__(self, code, farsi_name): + self._value_ = code + self.farsi_name = farsi_name + + AGRICULTURE = (1, "زراعت و خدمات وابسته") + FORESTRY_FISHING = (2, "جنگلداري و ماهيگيري") + COAL_MINING = (10, "استخراج زغال سنگ") + OIL_GAS_EXTRACTION = (11, "استخراج نفت گاز و خدمات جنبي جز اكتشاف") + METAL_ORE_MINING = (13, "استخراج كانه هاي فلزي") + OTHER_MINING = (14, "استخراج ساير معادن") + REMOVED_FOOD_BEVERAGE = (15, "حذف شده- فرآورده‌هاي غذايي و آشاميدني") + TEXTILES = (17, "منسوجات") + LEATHER_FOOTWEAR = (19, "دباغي، پرداخت چرم و ساخت انواع پاپوش") + WOOD_PRODUCTS = (20, "محصولات چوبي") + PAPER_PRODUCTS = (21, "محصولات كاغذي") + PUBLISHING_PRINTING = (22, "انتشار، چاپ و تكثير") + OIL_COAL_NUCLEAR = (23, "فراورده هاي نفتي، كك و سوخت هسته اي") + REMOVED_CHEMICALS = (24, "حذف شده-مواد و محصولات شيميايي") + RUBBER_PLASTIC = (25, "لاستيك و پلاستيك") + COMPUTER_ELECTRONIC_OPTICAL = (26, "توليد محصولات كامپيوتري الكترونيكي ونوري") + BASIC_METALS = (27, "فلزات اساسي") + METAL_PRODUCTS = (28, "ساخت محصولات فلزي") + MACHINERY_EQUIPMENT = (29, "ماشين آلات و تجهيزات") + ELECTRICAL_MACHINERY = (31, "ماشين آلات و دستگاه‌هاي برقي") + COMMUNICATION_DEVICES = (32, "ساخت دستگاه‌ها و وسايل ارتباطي") + MEDICAL_OPTICAL_MEASURING = (33, "ابزارپزشكي، اپتيكي و اندازه‌گيري") + AUTOMOTIVE_PARTS = (34, "خودرو و ساخت قطعات") + OTHER_TRANSPORT_DEVICES = (35, "ساير تجهيزات حمل و نقل") + FURNITURE_OTHER = (36, "مبلمان و مصنوعات ديگر") + SUGAR = (38, "قند و شكر") + MULTI_SECTOR_INDUSTRIAL = (39, "شركتهاي چند رشته اي صنعتي") + ELECTRICITY_GAS_STEAM_HOT_WATER = (40, "عرضه برق، گاز، بخاروآب گرم") + WATER_TREATMENT_DISTRIBUTION = (41, "جمع آوري، تصفيه و توزيع آب") + FOOD_BEVERAGE_EXCEPT_SUGAR = (42, "محصولات غذايي و آشاميدني به جز قند و شكر") + PHARMACEUTICALS = (43, "مواد و محصولات دارويي") + CHEMICAL_PRODUCTS = (44, "محصولات شيميايي") + INDUSTRIAL_CONTRACTING = (45, "پيمانكاري صنعتي") + WHOLESALE_EXCEPT_VEHICLES = (46, "تجارت عمده فروشي به جز وسايل نقليه موتور") + RETAIL_EXCEPT_VEHICLES = (47, "خرده فروشي،باستثناي وسايل نقليه موتوري") + CERAMICS = (49, "كاشي و سراميك") + TRADE_VEHICLES = (50, "تجارت عمده وخرده فروشي وسائط نقليه موتور") + AIR_TRANSPORT = (51, "حمل و نقل هوايي") + STORAGE_TRANSPORT_SUPPORT = (52, "انبارداري و حمايت از فعاليتهاي حمل و نقل") + CEMENT_LIME_PLASTER = (53, "سيمان، آهك و گچ") + OTHER_NON_METALLIC_MINERALS = (54, "ساير محصولات كاني غيرفلزي") + HOTELS_RESTAURANTS = (55, "هتل و رستوران") + INVESTMENTS = (56, "سرمايه گذاريها") + BANKS_CREDIT_INSTITUTIONS = (57, "بانكها و موسسات اعتباري") + OTHER_FINANCIAL_INTERMEDIARIES = (58, "ساير واسطه گريهاي مالي") + PRIORITY_RIGHTS_HOUSING = (59, "اوراق حق تقدم استفاده از تسهيلات مسكن") + TRANSPORT_STORAGE_COMMUNICATIONS = (60, "حمل ونقل، انبارداري و ارتباطات") + WATER_TRANSPORT = (61, "حمل و نقل آبي") + TRANSPORT_SUPPORT = (63, "فعاليت هاي پشتيباني و كمكي حمل و نقل") + TELECOMMUNICATIONS = (64, "مخابرات") + FINANCIAL_MONETARY_INTERMEDIARIES = (65, "واسطه‌گري‌هاي مالي و پولي") + INSURANCE_PENSION_FUND = (66, "بيمه وصندوق بازنشستگي به جزتامين اجتماعي") + AUXILIARY_FINANCIAL_INSTITUTIONS = (67, "فعاليتهاي كمكي به نهادهاي مالي واسط") + NEGOTIABLE_INVESTMENT_FUND = (68, "صندوق سرمايه گذاري قابل معامله") + FINANCIAL_SECURITIES = (69, "اوراق تامين مالي") + REAL_ESTATE = (70, "انبوه سازي، املاك و مستغلات") + ENGINEERING_TECHNICAL_ANALYSIS = (71, "فعاليت مهندسي، تجزيه، تحليل و آزمايش فني") + COMPUTER_RELATED_ACTIVITIES = (72, "رايانه و فعاليت‌هاي وابسته به آن") + INFORMATION_COMMUNICATIONS = (73, "اطلاعات و ارتباطات") + TECHNICAL_ENGINEERING_SERVICES = (74, "خدمات فني و مهندسي") + INTELLECTUAL_PROPERTY_SECURITIES = (76, "اوراق بهادار مبتني بر دارايي فكري") + RENTING_LEASING = (77, "فعالبت هاي اجاره و ليزينگ") + ADMINISTRATIVE_SUPPORT = (82, "فعاليت پشتيباني اجرائي اداري وحمايت كسب") + ART_ENTERTAINMENT_CREATIVE = (90, "فعاليت هاي هنري، سرگرمي و خلاقانه") + CULTURAL_SPORTS = (93, "فعاليتهاي فرهنگي و ورزشي") + INACTIVE_SECURITIES = (98, "گروه اوراق غيرفعال") + INDEX = ("X1", "شاخص") diff --git a/tehran_stocks/data/instrument_types.py b/tehran_stocks/data/instrument_types.py new file mode 100644 index 0000000..a365b16 --- /dev/null +++ b/tehran_stocks/data/instrument_types.py @@ -0,0 +1,40 @@ +from enum import Enum, unique + + +@unique +class InstrumentType(Enum): + Stock_Exchange_Stocks = "O1" # O1 + Farabourse_Stocks = "O3" # O3 + Futures = "O4" # O4 + Small_Company_in_Farabourse = "O5" # O5 + Mortgage_Securities = "O6" # O6 + Base_Market_Stocks = "O7" # O7 + Call_Option = "OA" # OA + Put_Option = "OF" # OF + Subordinate_Put_Option = "S4" # S4 + Commodities = "K1" # Gold Coin, Saffron, Pistachio, Rice + Unknown = "K2" # Original: ??? + Fund = "T1" # T1 + Farabourse_Fund = "T3" # T3 + Commodity_Forward = "BK" # BK + Murabaha_Sukuk = "B3" # B3 + Participation_Bonds = "B5" # B5 + Governmental_Murabaha = "B4" # B4 + Leasing_Sukuk = "B6" # B6 + GAM_Sukuk = "B7" # B7 + Right_of_Preemption = "R5" # R5 + + +# all O* and all T* +basic_instrument_types = [ + InstrumentType.Stock_Exchange_Stocks.value, + InstrumentType.Farabourse_Stocks.value, + InstrumentType.Futures.value, + InstrumentType.Small_Company_in_Farabourse.value, + InstrumentType.Base_Market_Stocks.value, + InstrumentType.Call_Option.value, + InstrumentType.Put_Option.value, + InstrumentType.Commodities.value, + InstrumentType.Fund.value, + InstrumentType.Farabourse_Fund.value, +] diff --git a/tehran_stocks/data/tickers.py b/tehran_stocks/data/tickers.py new file mode 100644 index 0000000..9284f2e --- /dev/null +++ b/tehran_stocks/data/tickers.py @@ -0,0 +1,846 @@ +from enum import Enum, unique + + +@unique +class TickerEnum(Enum): + ASP = 17617474823279712 + AALYS = 34213522001938649 + AATBAR = 35163287528816137 + AATLA = 36773155987365094 + AATMAD = 66818022341772870 + ABAD = 59612098290740355 + ABADA = 37661500521100963 + ABHMN = 1185549032856396 + ABYN = 9987529074833218 + AFAGH = 37073830945037165 + AFGHMLT = 15451317146134956 + AFRA = 52792903131341205 + AFRAN = 3846143218462419 + AGAS = 33887145736684266 + AGHYGH = 45452221088910484 + AHRM = 17914401175772326 + AKAM = 490987973229371 + AKHABR = 22811176775480091 + AKSYZHN = 50094941173290382 + AKVRD = 30282299500988269 + ALA = 19828734979381742 + ALBRZ = 70270965300262393 + ALMAS = 28788598290160782 + ALTVN = 28374437855144739 + AMYD = 18599703143458101 + AMYN = 58873907630765023 + AMYNYKM = 45728383369147894 + ANAR = 30215634246748564 + ANRZHY = 49641108336531623 + ANTKHAB = 10236455588057352 + AP = 55254206302462116 + APAL = 655060129740445 + APRDAZ = 63830424809501048 + ARAM = 14230681955555738 + ARDSTAN = 35331248532537562 + ARFA = 59266699437480384 + ARMGHAN = 31366347648583654 + ARYA = 55127657985997520 + ARYAN = 6506179926371994 + ARZSH = 43443105991896600 + ARZSHMSKN = 71945594172117613 + ASAM = 36592972482259020 + ASAMYD = 24869832924911721 + ASAS = 66682662312253625 + ASTGHLAL = 22259718159702272 + ASTYL = 24907784641855842 + ASYA = 51106317433079213 + ASYATK = 14079693677610396 + ATKAM = 27405735172634593 + ATKAY = 39751275523025334 + ATLS = 11427939669935844 + ATYMS = 22839330962768817 + AVA = 18007109712724189 + AVAN = 69847139870135237 + AVJ = 62575434414985179 + AVND = 31039212000825988 + AVNYKS = 23498719713662118 + AVSTA = 57761388729898548 + AYAR = 34144395039913458 + AYNDH = 17933573078185644 + BALAS = 8646067353086740 + BALBR = 62952165421099192 + BARAN = 26882843763780650 + BAYKA = 23891830829322971 + BAZDH = 34856765062083074 + BAZRGAM = 47797676603278665 + BFJR = 46178280540110577 + BGYLAN = 71068313834275501 + BHPAK = 12746730665870442 + BHY_V = 42799209630949274 + BHYR = 60289595205403229 + BJHRM = 33629260529503413 + BKAB = 70219663893822560 + BKABL = 62107151670661969 + BKAM = 22382156782768756 + BKHAVR = 5599691633622269 + BKHNVJ = 7628308021169118 + BMPNA = 39807886630843041 + BMVLD = 48261930411425125 + BMVTV = 22086876724551482 + BMVTVH = 1931037287459558 + BNV = 54493234408301135 + BNYRV = 20453828618330936 + BPAS = 51971068201094874 + BPYVND = 64619251116188373 + BRKT = 34557241988629814 + BRKTH = 8981227815720204 + BRLYAN = 4216536645718658 + BSAMA = 41625340598198551 + BSHHAB = 69454539056549106 + BSVYCH = 47377315952751604 + BTRANS = 46752599569017089 + BVALY = 50247622569476338 + BVRS = 60523145697836739 + BZAGRS = 29882244560576007 + BZR = 37222720235819361 + CHAFST = 23936607891892333 + CHDN = 12329519546621752 + CHFYBR = 64358061873294912 + CHKAPA = 28957320033282870 + CHKARN = 53113471126689455 + CHKAVH = 24254843881948059 + CHKHZR = 41122066907413786 + DABVR = 61332057061846617 + DABYD = 49054891736433700 + DALBR = 60451823714332895 + DAMYN = 50100062518826135 + DANA = 48511238766369097 + DANYK = 67717913151786055 + DARA = 62012736978844991 + DAR_KM = 62235397452612911 + DARAB = 27299841173245405 + DARV = 67988012428906654 + DARYK = 71076372178147339 + DARYVSH = 58789178087946067 + DASVH = 12387472624849835 + DAVH = 5305844922895340 + DDAM = 66450490505950110 + DDANA = 69540585676934415 + DFARA = 56550776668133562 + DFRA = 18303237082155264 + DGHAZY = 46801030346547943 + DHAVY = 43374709930371268 + DHDSHT = 15374483986949695 + DJABR = 33406621820337161 + DKPSVL = 52382684379473036 + DKVSR = 23353689102956991 + DKYMY = 20024911381434086 + DLGHMA = 29247915161590165 + DLR = 4384288570322406 + DMAVND = 66142616039907394 + DPARS = 70474983732269112 + DRAZK = 22255783119783047 + DRAZY = 16567465928886309 + DRHAVR = 13243992182070788 + DRSA = 34581754264880199 + DRYA = 11285885633824855 + DRYN = 21077182490095731 + DSANKV = 47348197320716810 + DSBHA = 43622578471330344 + DSBHAN = 5866848234665627 + DSHYMY = 33603212156438463 + DSHYRY = 63084741752814852 + DSYNA = 11432067920374603 + DTHRAN = 14985138705106402 + DTMAD = 34641719089573667 + DTVLYD = 18093681647131179 + DTVZYA = 66726992874614788 + DY = 44818950263583523 + DYBA = 70698996132397388 + DYRAN = 69090868458637360 + DYRANH = 25752723009842207 + DZHRAVY = 8915450910866216 + FAFGH = 22424135367941584 + FAHVAZ = 44296315953738727 + FAKHR = 56344907495802692 + FALVM = 19367527798307032 + FAMA = 34673681828119297 + FARAK = 20865316761157979 + FARS = 25244329144808274 + FASMYN = 66701874099226162 + FAYRA = 65004959184388996 + FAZR = 38547060135156069 + FBAHNR = 66772024744156373 + FBSTM = 4159532151694984 + FBYRA = 49399017998386087 + FGHDYR = 28147934478934110 + FGSTR = 8725363201030474 + FJAM = 30765727085936322 + FJHAN = 45507655586782998 + FJR = 41302553376174581 + FJVSH = 64485827086284311 + FKHAS = 4733285133017464 + FKHVZ = 28864540805361867 + FKMND = 70309338813767186 + FLAMY = 25286509736208688 + FLAT = 60633055620418060 + FLVLH = 48623320733330408 + FMAK = 35445515321658835 + FMLY = 35425587644337450 + FMRAD = 18004480270695404 + F_FZAR = 69171897374421261 + F_VA = 55201604487356053 + FNFT = 59342912854668427 + FNVAL = 57875847776839336 + FNVRD = 56324206651661881 + FPNTA = 68488673556087148 + FRABVRS = 58741071099161284 + FRAVR = 408934423224097 + FRAZ = 13666407494621646 + FRDA = 65249046611427924 + FRVD = 51017863148152520 + FRVS = 54419429862704331 + FRVSYL = 44846320603450383 + FRVY = 29974853866926823 + FRVZH = 69817338460284329 + FSA = 318005355896147 + FSAZAN = 12874072841236826 + FSBZVAR = 37284308569715577 + FSDYD = 20966291817819448 + FSPA = 8977441217024425 + FSRB = 54277068923045214 + FSVZH = 56375665074210467 + FTVSA = 55373808401388162 + FVKA = 66514709341259550 + FVLAD = 46348559193224090 + FVLAZH = 40808043719554948 + FYRVZA = 10795723506538053 + FZR = 8175784894140974 + FZRYN = 43716452378323683 + GARANTY = 971068957336171 + GDNA = 10114441830266109 + GHALBR = 24303422207378456 + GHARVM = 10831074117626896 + GHASM = 34540569618314880 + GHAZR = 35158826900216508 + GHBHAR = 71666521540545716 + GHBHNVSH = 17059960254855208 + GHBSHHR = 42387718866026650 + GHCHAR = 23600798892801694 + GHCHYN = 44850033148208596 + GHDAM = 2254054929817435 + GHDANH = 8234855558996646 + GHDYS = 3492952121304423 + GHFARS = 16410724132711490 + GHGL = 22299894048845903 + GHGLPA = 14312030900097668 + GHGLSTA = 64843936383937546 + GHGRJY = 31024260997481994 + GHGYLA = 57300230097485720 + GHGZ = 57551382352708199 + GHHKMT = 14398278072324784 + GHKVRSH = 62404730109947970 + GHLRST = 56820995669577571 + GHMARG = 21501758781665781 + GHMAYH = 59461185672081215 + GHMHRA = 6131290133202745 + GHMRV = 43342306308122676 + GHMYNV = 5516102131364383 + GHNAB = 42470251469508137 + GHNGHSH = 3050342257199174 + GHNVSH = 48619517949257749 + GHNYLY = 21426277483799140 + GHNYSHA = 63380098535169030 + GHPAK = 34032872653290886 + GHPAZR = 45518744711972166 + GHPVNH = 61978776664766359 + GHPYNV = 18401147983387689 + GHPYRA = 67030488744129337 + GHRN = 39610074039667804 + GHSABT = 44967158778304588 + GHSALM = 28672095850798501 + GHSFHA = 40411537531154482 + GHSHAN = 31791737198597563 + GHSHAZR = 59921975187856916 + GHSHHD = 37631109616997982 + GHSHHDAB = 33611155027418901 + GHSHKR = 35964395659427029 + GHSHRYN = 36671655475498480 + GHSHSFA = 61506294208022391 + GHSHVKV = 51294484197536070 + GHSHYR = 71523986304961239 + GHSYNV = 71758511001096824 + GHVYTA = 37842793167868642 + GHYSTV = 4267564158935326 + GHYSTVH = 66974398910717440 + GHZR = 4369934250728330 + GHZVYN = 15259343650667588 + GKVSR = 66599109405217136 + GKYSH = 44665761767777759 + GLDYRA = 54715710303837090 + GNGYN = 59470107928175959 + GNJ = 58514988269776425 + GNJYN = 57728534324022361 + GNJYNH = 47101579271117172 + GPARS = 59848307608894801 + GSHAN = 70391097626818082 + GVHR = 12390706505809150 + HAFRYN = 21432551703060846 + HAMRZ = 50503654866742146 + HARYA = 56798822689379375 + HASA = 44625249840480397 + HA_B = 43362635835198978 + HBNDR = 63704201144621295 + HFARS = 35424116338766901 + HGHR = 47756003257788498 + HGRDSH = 53334304751609770 + HJRT = 5317427172344706 + HKHZR = 28253678449273505 + H_ZN = 47041908051542008 + HMAY = 15494954332657697 + HMRAH = 68635710163497089 + HPARSA = 917857106093847 + HPRTV = 4885314541377431 + HPTRV = 16369313804633525 + HRHSHA = 30443839313522574 + HRMZ = 70498485598181604 + HRYL = 5564768007356822 + HSHKVH = 50587892784913370 + HSYNA = 1625149423498289 + HSYR = 13281937213456378 + HTAYD = 3722699128879020 + HTVKA = 22260326095996531 + HVSHYAR = 9375107506435996 + HYVA = 62845384302495432 + JHRM = 49674915481184052 + JHSH = 7681671915916933 + JM = 32357363984168442 + J_YLN = 27096851668435724 + JVYN = 23269285154135003 + KABGN = 10654052153538617 + KAJ = 42670427020727409 + KALA = 44549439964296944 + KAMA = 4942127026063388 + KAMYAB = 16040900750729921 + KARA = 71843282162462661 + KARAM = 69904617508102874 + KARDAN = 65023851436340574 + KARYN = 16056283141617755 + KARYS = 69067576215760005 + KASPYN = 28431095903407567 + KAVH = 60350996279289099 + KAYTA = 45608932669358493 + KAYZD = 58810336532668771 + KAZR = 49353447565507376 + KAZRV = 16777570760181431 + KBAFGH = 43256212620530446 + KCHAD = 18027801615184692 + KDMA = 3623921205367364 + KFPARS = 19471788163911687 + KFRA = 23837844039713715 + KFRAVR = 49627523909849331 + KGAZ = 62346804681275278 + KGHR = 30703140537034664 + KGHZVY = 63499217872110599 + KGL = 35700344742885862 + KHAFZ = 32257753560585502 + KHAHN = 7483280423474368 + KHAMRA = 6433335428452486 + KHATM = 18865325633315847 + KHAVR = 49270349234092953 + KHAZYN = 42075223783409640 + KHBAZRS = 19310456400689867 + KHBHMN = 26824673819862694 + KHBNYAN = 37089148943784077 + KHCHRKHSH = 33783140337377394 + KHDYZL = 38084304113529336 + KHFNAVR = 58180284328186631 + KHFNR = 28033133021443774 + KHFVLA = 23049019886587905 + KHGSTR = 48990026850202503 + KHKAR = 59217041815333317 + KHKAVH = 64842837716888827 + KHKMK = 19257295292088310 + KHKRMAN = 58482817740704549 + KHLNT = 14957056743925737 + KHLYBL = 50117925085549635 + KHMHR = 17330546482145553 + KHMHRKH = 39436183727126211 + KHMHVR = 7457232989848872 + KHMVTVR = 57273529732791251 + KHNSYR = 17834623106317041 + KHPARS = 25211433301660888 + KHPVYSH = 4758266259250794 + KHRAM = 25631699615003698 + KHRASAN = 43552974795606067 + KHRBA = 25559236668122210 + KHRYNG = 45895339414786358 + KHSAPA = 44891482026867833 + KHSDRA = 2318736941376687 + KHSHRGH = 63915926161403347 + KHTRAK = 22903901709044823 + KHTVGHA = 70289374539527245 + KHTVR = 50185721305191887 + KHVDKFA = 22956708386610464 + KHVDRV = 65883838195688438 + KHVRSHYD = 4523009251964699 + KHVSAZ = 31879190587976736 + KHZAMYA = 2589887561569709 + KHZR = 32821908911812078 + KKHAK = 16405556680571453 + KLR = 1822787329898392 + KLVND = 32678431934327184 + KMASH = 67690708346979840 + KMND = 34718633636164421 + KMNGNZ = 50341528161302545 + KMRJAN = 50633804639547462 + KMRJANH = 39683880301779506 + KMYNA = 54263829393913132 + KNVR = 20411759370751096 + KPARS = 9698674686691945 + KPRVR = 22941065011246116 + KPSHYR = 57639364758870873 + KRAZY = 57086055330734195 + KRMAN = 50792786683910016 + KRMASHA = 38437201078089290 + KRVY = 22787503301679573 + KSADY = 29122854902865456 + KSAPA = 28325731560106431 + KSAPAH = 25879082553156895 + KSAVH = 25001509088465005 + KSDF = 42696242981550091 + KSHRGH = 42690477960659940 + KSRA = 4614779520007780 + KSRAM = 20560887114747719 + KTBS = 8977369674477111 + KTRAM = 24085906177899789 + KTVKA = 65671173927025645 + KTVSAH = 23374429962331387 + KVRZ = 42049553761321495 + KVSR = 22275596386264204 + KVYR = 43545527030854340 + K__Y = 62977319271289925 + KYA = 3542690854557886 + KYAN = 53251602435454519 + KYMYA = 14427168056799610 + KYMYATK = 39884565732277052 + KYSVN = 49953653111442595 + KZGHAL = 28291104595448527 + LABSA = 63363116407864462 + LAZMA = 50368344235826302 + LBKHND = 31569200988534548 + LBVTAN = 30650426998863332 + LKHANH = 56006915451245411 + LKHZR = 65414507129586385 + LKMA = 45641540066710190 + LPARS = 5187018329202415 + LPYAM = 793710053482057 + LSRMA = 55897939403232751 + LTYF = 16422980660132735 + LVTVS = 59142194115401696 + MA = 29860265627578401 + MADYRA = 15917865009187760 + MANY = 61265100181977543 + MARVN = 53449700212786324 + MBYN = 27922860956133067 + MDARAN = 65999092673039059 + MDYR = 65576885779918210 + MDYRYT = 12490072956930435 + MFAKHR = 4247709727327181 + MLT = 13611044044646901 + MMSNY = 7505990056227818 + MRGHAM = 25020657188512139 + MRVARYD = 31248540252187559 + MSGHAL = 32469128621155736 + MYDKV = 33441514568901717 + MYHN = 26543014712914772 + NAB = 30582275818828857 + NAMA = 10411249540376641 + NBRVJ = 10843114830116591 + NFYS = 4626686276232042 + NGYN = 10145129193828624 + NHAL = 12913156843322499 + NKHL = 27797446447955609 + NMRYNV = 30231789123900526 + NSHAN = 1241998328504490 + NSHAR = 49129081625829210 + NTRYN = 64699417405634265 + NTVS = 22950683624908253 + NVRY = 19040514831923530 + NVYN = 59866041653103343 + NYLY = 31188566503248753 + NYRV = 39481233087768672 + PADA = 67522512921942106 + PADASH = 43267179898797137 + PAKSHV = 62786156501584862 + PALAYSH = 67675656072510693 + PARND = 70595828753641750 + PARS = 6110133418282108 + PARSAN = 23441366113375722 + PARSYAN = 9481703061634967 + PARTA = 72044846109864381 + PASA = 63580313877463104 + PAYA = 55070742656326885 + PDRKHSH = 24079409192818584 + PKHSH = 12638840758449459 + PKHSHH = 71429146297698274 + PKRMAN = 23214828924506640 + PKVYR = 7235435095059069 + PLASK = 57722642338781674 + PLAST = 52593789542874668 + PLVLH = 29316948750916349 + PRDAKHT = 59607545337891226 + PRDYS = 15039949673085566 + PRTV = 48818952524587858 + PSHAHN = 44052047028305231 + PSHND = 10120557300120078 + PTAYR = 41935584690956944 + PTRVAGAH = 37828981835497620 + PTRVDARYVSH = 2019449432639594 + PTRVL = 69143674941561637 + PTRVMA = 7670135462634715 + P_AD = 26316376625263940 + PYZD = 32784604551756178 + RABYN = 33527290777160784 + RAFZA = 68941822863885255 + RAFZAH = 40210715138931726 + RANFVR = 40505767672724777 + RAYKA = 66105959479616770 + RKYSH = 27952969918967492 + RMAS = 22002589755112021 + RMPNA = 67126881188552864 + RNYK = 33854964748757477 + RSHD = 48287767791629523 + RTAP = 41048299027409941 + RTKV = 49854144784855542 + RVYSH = 15124889255100138 + RYSHMK = 6478064539164167 + SAB_KSYZHN = 44558786393585356 + SABYK = 70883594945615893 + SAHL = 62708526880913292 + SAKHT = 17800036702302776 + SALVND = 16693610252404739 + SAM = 55308018877404137 + SAMAN = 38179358042686391 + # SAMAN = '66456062140680461' + SAMRA = 15726796686853780 + SAMYD = 18568733593280948 + SARAB = 4563413583000719 + SARBYL = 34890845654517313 + SARVJ = 25417476013479486 + SARVM = 15949743338644220 + SASFA = 7503669593172728 + SATMA = 64707090254488560 + SAVH = 32347247706508046 + SAYNA = 64298008532791199 + SAYND = 45205530868811305 + SAYRA = 62558705479545830 + SAZHN = 40650252484299134 + SAZRY = 64155926828410021 + SBA = 45392752356003555 + SBAGH = 68909035712962732 + SBAGHR = 24807173016704795 + SBAT = 26780282166315918 + SBHAN = 32525655729432562 + SBHSAZ = 14744445176220774 + SBJNV = 66295665969375744 + SBZVA = 611986653700161 + SDBYR = 64341992373049080 + SDF = 51285326016186930 + SDSHT = 27000326841257664 + SDVR = 27218386411183410 + SFANV = 4528607775462304 + SFAR = 41227201752535311 + SFARS = 15521712617204216 + # SFARS = '30852391633490755' + SFARVD = 4686607974846832 + SFASY = 34721884030854211 + SGHAYN = 60654872678917533 + SGHDYR = 21096748051392414 + SGHRB = 17939384202383793 + # SGHRB = '52220424531578944' + SGHZVY = 12965822877128721 + SHAM = 38713440086361985 + SHAMLA = 16959429956899455 + SHARAK = 7711282667602555 + SHARVM = 3407806799514469 + SHAVAN = 60247433951600827 + SHBHRN = 22667016906590506 + SHBNDR = 35366681030756042 + SHBRYZ = 48753732042176709 + SHBSYR = 68517032834363488 + SHDVS = 40611478183231802 + SHFA = 36899214178084525 + SHFARA = 16673205196919832 + SHFARS = 43781018754867729 + SHFN = 65122215875355555 + SHGAMRN = 1050751214677134 + SHGHDYR = 21772258644715569 + SHGL = 44153164692325703 + SHGMT = 41284516796232939 + SHGVYA = 5987841496184505 + SHJM = 6116572045021585 + SHKAM = 69446612239102459 + SHKBYR = 56574323121551263 + SHKF = 58602432837130018 + SHKHARK = 70934270174405743 + SHKLR = 62177651435283872 + SHKRBN = 27308217070238237 + SHLAAB = 39116664428676213 + SHLYA = 26025177574491991 + SHMLY = 55862580907068610 + SHMVAD = 28251956446987982 + SHNFT = 14073782708315535 + SHPAKSA = 11622051128546106 + SHPARS = 61102694810476197 + SHPAS = 35178706978554988 + SHPDYS = 20562694899904339 + SHPLY = 28845264556937486 + SHPNA = 7745894403636165 + SHPTRV = 71957984642204570 + SHRANL = 44013656953678055 + SHRAZ = 14031158866706953 + SHRKHYZ = 51200575796028449 + SHRMZ = 29747059672582491 + SHRNGY = 40025799067544201 + SHSAKHT = 15282093177363578 + SHSDF = 204092872752957 + SHSFHA = 18346219759153870 + SHSM = 59800986739603675 + SHSPA = 49188729526980541 + SHSTAN = 3173544097113770 + SHSYNA = 30974710508383145 + SHTAB = 64216772923447100 + SHTHRAN = 31049085025064185 + SHTRAN = 51617145873056483 + SHTVKA = 38555056423456635 + SHTVLY = 66210395067138534 + SHVYNDH = 3493306453706327 + SHYRAN = 35796086458096255 + SHYRAZ = 38568786927478796 + SHZNG = 65490886290565185 + SJAM = 28328083629154916 + SJNVB = 60887982279284651 + SJVAN = 30507152381699953 + SKARVN = 15930821245168534 + SKHASH = 4470657233334072 + SKHND = 59598536122397373 + SKHVAF = 55959112038778737 + SKHVZ = 41974758296041288 + SKHZR = 67327029014085707 + SKRD = 65321970913593427 + SKRMA = 15472396110662150 + SLAM = 70541934393301867 + SLAR = 61664227282090067 + SMAYH = 43913530989262989 + SMAZN = 33808206014018431 + SMGA = 46741025610365786 + SMSKN = 3863538898378476 + SMTAZ = 58035444268544991 + SMYN = 66721204145017523 + SNA = 32112121249636248 + SNAM = 831325835570803 + SNHAL = 52846735736632974 + SNM = 47125023640770480 + SNVR = 63315013743060811 + SNVSA = 32845891587040106 + SNVYN = 36995197800118822 + # SNVYN = '68203878405672734' + SNYR = 14231831499205396 + SNZAM = 45066064863062755 + SP = 71856634742001725 + SPAHA = 35669480110084448 + SPAS = 39453972158399542 + SPR = 17226661368470120 + SPRDYS = 53999651992586159 + SPRMY = 27668158733246204 + SPYD = 45519261544951819 + SPYDAR = 29590002988360984 + SPYDMA = 2161110547458064 + SRCHSHMH = 35948133957468680 + SRV = 64942549055019553 + SRVD = 11964419322927535 + # SRVD = '18883380772506226' + SRVTM = 71672399601682259 + SSFHA = 10568944722570445 + SSHAHD = 63481599728522324 + SSHMAL = 6757220448540984 + SSHRGH = 26997316501080743 + # SSHRGH = '6043384171800349' + SSVFY = 13227300125161435 + STRAN = 20926459161497908 + # STRAN = '30829203706095076' + STVSA = 47702059190622416 + SYDKV = 37281199178613855 + SYLAM = 14617104402836487 + SYMRGH = 28450080638096732 + SYNAD = 31913287805282551 + SYSTM = 47749661205825616 + SYTA = 10171945867136336 + TABA = 51459202425114449 + TAPYKV = 22560050433388046 + TARAZ = 24651394045981418 + TASYKV = 23293437377896568 + TATMS = 35543935713999309 + TAYRA = 19298748452450329 + TBRK = 53204330224889981 + TFARS = 22276798221643766 + TFYRV = 45062188442385800 + TJLY = 1301069819790264 + TKARDAN = 52932092555708556 + TKMBA = 30719054967088301 + TKNAR = 52455922800537930 + TKNV = 3654864906585643 + TKSHA = 62258804563636993 + TKYMYA = 66643284949247248 + TLA = 46700660505281786 + TLVA = 19060410060488876 + TLYSH = 41781090739318251 + TMAVND = 60162288821230099 + TMAVNDH = 9546211590069761 + TMHRKH = 22427604495160869 + TMLT = 11129387075131725 + TMSHK = 53145304508578701 + TNVYN = 25357135030606405 + TNVYNH = 379466056274101 + TPKV = 54509759694064219 + TPMPY = 43062880954780884 + TPSY = 3839324986781871 + TPVLA = 22308305646551497 + TRMH = 42705920381780437 + TSHTAD = 66127247173352975 + TSMYM = 53419976284977130 + TVAN = 41927452991671109 + TVRYL = 37389789764168256 + TVSKA = 56871139881800017 + TVSN = 66315581735594751 + TYAM = 16578517055478811 + TYPYKV = 29758477602878557 + VAATBAR = 47841327496247362 + VAFR = 45991797190214892 + VAFRY = 589599697502308 + VAHSA = 53647874954005806 + VAHYA = 17284166795866794 + VALBR = 57944184894703821 + VALMAS = 36282416082320053 + VAMYD = 52232388263291380 + VAMYN = 4373573287489723 + VAMYR = 67213778593531096 + VARS = 53686258677793038 + VARYN = 24212636157410845 + VATVS = 17269972595370241 + VATY = 33541897671561960 + VAVA = 22490169030401337 + VAYRA = 9141577977527107 + VAYRAN = 3149396562827132 + VAZR = 1358190916156744 + VBANK = 48010225447410247 + VBAZAR = 41286608288791633 + VBHMN = 18063426072758458 + VBMLT = 778253364357513 + VBRGH = 61298636307861167 + VBSHHR = 13937270451301973 + VBVALY = 28328710198554144 + VBYMH = 11773403764702778 + VDANA = 66424163876658304 + VDY = 43966385447049549 + VFTKHAR = 27148572013604038 + VGHDYR = 26014913469567886 + VGRDSH = 7920014658832193 + VGSTR = 43951910415124966 + VHAMVN = 19348717261145458 + VHKMT = 12777578088653944 + VHNR = 60783654574662426 + VHVR = 25215182208950217 + VKADV = 56717416662584054 + VKAR = 47996917271187218 + VKBHMN = 64973252728260903 + VKGHDYR = 40553302624764543 + VKHAVR = 47333458678352378 + VLANA = 66830065858417081 + VLBHMN = 48287670503317419 + VLBHMNH = 32575690244214247 + VLGHDR = 23086515493897579 + VLGHMAN = 31959715133485440 + VLKAR = 61469668095573716 + VLMLT = 11403770140000603 + VLPARS = 48241092863917835 + VLRAZ = 12901875871456398 + VLSAPA = 45174198424472334 + VLSHRGH = 31078457170311964 + VLSNM = 71744682148776880 + VLTJAR = 34621618468546063 + VLYZ = 56802926348928053 + VMAADN = 58931793851445922 + VMALM = 6847536925606808 + VMHAN = 47026464823464687 + VMLL = 24644999329120295 + VMLT = 10055255678920880 + VMLY = 41796741644273824 + VMSHAN = 35369183060321179 + VNFT = 33931218652865616 + VNVYN = 47302318535715632 + VNYKY = 25336820825905643 + VNYRV = 62603302940123327 + VPARS = 33293588228706998 + VPASAR = 9536587154100457 + VPKHSH = 7183333492448248 + VPSA = 11560002870991149 + VPST = 22087269603540841 + VPTRV = 59486059679335017 + VPVYA = 24785665268004766 + VRAZY = 60079434631497942 + VRNA = 7385624172574740 + VSAKHT = 25514780181345713 + VSALT = 23175320865252772 + VSAPA = 37614886280396031 + VSBHAN = 43283802997035462 + VSHHR = 41379697187196382 + VSHMAL = 9761381741308262 + VSKAB = 11258722998911897 + VSKHVZ = 40043919653526083 + VSNA = 24662567615903665 + # VSNA = '46982154647719707' + VSNAT = 57309221039930244 + VSNDVGH = 37204371816016200 + VSNV = 44986797317463049 + VSPH = 2328862017676109 + VSPHH = 54287510798838485 + VSPHR = 114312662654155 + VSRMD = 43291783149314349 + VSVGH = 59839275647597021 + VSYN = 56591881518499520 + VSYNA = 45050389997905274 + VTAAVN = 5920901446678689 + VTJART = 63917421733088077 + VTVBY = 3955332316338258 + VTVKA = 47232550823972469 + VTVS = 68117765376081366 + VTVSA = 2944500421562364 + VTVSHH = 54676885047867737 + VTVSKA = 48457557221009333 + VTVSM = 17528249960294496 + VYSA = 69472361926040823 + VYSTA = 58852293795036597 + VZMYN = 19954896371640204 + YAGHVT = 1438514795814416 + YARA = 45284811973404357 + ZAGRS = 13235547361447092 + ZBYNA = 54482686501491508 + ZDSHT = 25180702353416009 + ZFJR = 36844527173896115 + ZFKA = 5427792638736934 + ZGHYAM = 71290297158948749 + ZGLDSHT = 10024128313803797 + ZKSHT = 7300125658580126 + ZKVSR = 42599305106713939 + ZMAHAN = 4507558419857064 + ZMAN = 38356837895042988 + ZMGSA = 5054819322815158 + ZMLARD = 14916489896692147 + ZNGAN = 67170215467608124 + ZNJAN = 12303918642491681 + ZPARS = 33420285433308219 + ZR = 33254899395816171 + ZRFAM = 33144542989832366 + ZRYN = 50139638026536387 + ZSHGZA = 26259366519412975 + ZSHRYF = 26547785441834730 + ZVB = 71483646978964608 + ZYTVN = 28551661889797217 diff --git a/tehran_stocks/download/__init__.py b/tehran_stocks/download/__init__.py new file mode 100644 index 0000000..0a106a7 --- /dev/null +++ b/tehran_stocks/download/__init__.py @@ -0,0 +1,9 @@ +from .names import fill_stock_table, get_stock_detail, get_stock_groups + +__all__ = [ + "fill_stock_table", + "get_stock_detail", + "get_stock_groups", + "get_all_price", + "update_group", +] diff --git a/tehran_stocks/download/base.py b/tehran_stocks/download/base.py new file mode 100644 index 0000000..496a477 --- /dev/null +++ b/tehran_stocks/download/base.py @@ -0,0 +1,50 @@ +from typing import Dict +import aiohttp + +from traitlets import Any + + +BASE_URL = "http://old.tsetmc.com" +NEW_BASE_URL = "http://www.tsetmc.com" +CDN_URL = "http://cdn.tsetmc.com" + + +class FetchMixin: + session = None + base_url = NEW_BASE_URL + old_base_url = BASE_URL + cdn_url = CDN_URL + headers = { + "Origin": "http://www.tsetmc.com", + "Pragma": "no-cache", + "Referer": "http://www.tsetmc.com/", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", + } + + @classmethod + async def _fetch_raw(cls, url: str, retries: int = 3) -> str: + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=cls.headers) as resp: + if resp.status != 200: + retries -= 1 + if retries > 0: + return await cls._fetch_raw(url, retries) + raise Exception( + f"Error fetching {url}: response code {resp.status}" + ) + res: str = await resp.text() + return res + + @classmethod + async def _fetch(cls, url: str, retries: int = 3) -> Dict[str, Any]: + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=cls.headers) as resp: + if resp.status != 200: + retries -= 1 + if retries > 0: + return await cls._fetch(url, retries) + raise Exception( + f"Error fetching {url}: response code {resp.status}" + ) + res: Dict[str, Any] = await resp.json() + return res diff --git a/tehran_stocks/download/details.py b/tehran_stocks/download/details.py new file mode 100644 index 0000000..772486f --- /dev/null +++ b/tehran_stocks/download/details.py @@ -0,0 +1,94 @@ +# Write a class that get data from different apis and returns data as a dataclass +# I will provide list of apis, create functions to get data from those apis +# then I will provide json schema for each api, create functions to validate data +# then use the schema to create dataclass +# then use the dataclass to return data +# everything is async, everything should handle errors +from datetime import datetime +from typing import Dict, Any, List, Optional +from .base import NEW_BASE_URL, CDN_URL, FetchMixin +from tehran_stocks.schema.details import ( + BestLimitHistory, + InstrumentInfo, + InstrumentState, + TradeClientType, + Trade, + ClosingPriceData, + BestLimit, + ShareHolderItem, +) + + +class InstrumentDetailAPI(FetchMixin): + """ + A class for fetching details of a stock from the Tehran Stock Exchange (TSE) using its instrument code. + + Attributes: + ins_code (str): The instrument code of the stock. + session (aiohttp.ClientSession): The HTTP session used for making requests. + base_url (str): The base URL for making requests to the TSE API. + cdn_url (str): The URL for the TSE Content Delivery Network (CDN) API. + """ + + def __init__(self, ins_code: int | str): + super().__init__() + self.ins_code = ins_code + self.session = None + self.base_url = NEW_BASE_URL + self.cdn_url = CDN_URL + + async def get_instrument_info(self) -> InstrumentInfo: + url = f"{self.cdn_url}/api/Instrument/GetInstrumentInfo/{self.ins_code}" + data = await self._fetch(url) + return InstrumentInfo(**data["instrumentInfo"]) + + async def get_codal(self) -> Dict[str, Any]: + url = f"{self.cdn_url}/api/Codal/GetPreparedDataByins_code/9/{self.ins_code}" + return await self._fetch(url) + + async def get_instrument_state_top(self) -> InstrumentState: + url = f"{self.cdn_url}/api/MarketData/GetInstrumentStateTop/1" + data = await self._fetch(url) + return InstrumentState(**data["instrumentState"][0]) + + async def get_client_type(self) -> TradeClientType: + url = f"{self.cdn_url}/api/ClientType/GetClientType/{self.ins_code}/1/0" + data = await self._fetch(url) + return TradeClientType(**data["clientType"]) + + async def get_trade(self) -> List[Trade]: + url = f"{self.cdn_url}/api/Trade/GetTrade/{self.ins_code}" + data = await self._fetch(url) + return [Trade(**i) for i in data["trade"]] + + async def get_closing_price_info(self) -> ClosingPriceData: + url = f"{self.cdn_url}/api/ClosingPrice/GetClosingPriceInfo/{self.ins_code}" + data = await self._fetch(url) + return ClosingPriceData(**data["closingPriceInfo"]) + + async def get_best_limits(self, date: str | datetime | None = None) -> BestLimit: + url = f"{self.cdn_url}/api/BestLimits/{self.ins_code}" + data = await self._fetch(url) + return BestLimit(**data["bestLimits"][0]) + + # http://cdn.tsetmc.com/api/BestLimits/48990026850202503/20231015 + async def get_best_limit_history( + self, date: str | datetime + ) -> List[BestLimitHistory]: + if isinstance(date, datetime): + date = date.strftime("%Y%m%d") + url = f"{self.cdn_url}/api/BestLimits/{self.ins_code}/{date}" + data = await self._fetch(url) + return [BestLimitHistory(**i) for i in data["bestLimitsHistory"]] + + # https://cdn.tsetmc.com/api/Shareholder/48990026850202503/20231015 + async def get_share_holder( + self, date: Optional[str | datetime] + ) -> List[ShareHolderItem]: + if date is None: + date = datetime.now().strftime("%Y%m%d") + elif isinstance(date, datetime): + date = date.strftime("%Y%m%d") + url = f"{self.cdn_url}/api/Shareholder/{self.ins_code}/{date}" + data = await self._fetch(url) + return [ShareHolderItem(**i) for i in data["shareShareholder"]] diff --git a/tehran_stocks/download/index.py b/tehran_stocks/download/index.py new file mode 100644 index 0000000..a3933d8 --- /dev/null +++ b/tehran_stocks/download/index.py @@ -0,0 +1,101 @@ +from typing import List +from tehran_stocks.schema.index import IndexHistoryItem +from .details import InstrumentDetailAPI +from enum import Enum + + +class IndexType(Enum): + AGRICULTURE = 34408080767216529 + COAL_MINING = 19219679288446732 + METAL_ORE_MINING = 13235969998952202 + OTHER_MINERALS = 62691002126902464 + TEXTILES = 59288237226302898 + LEATHER_PRODUCTS = 69306841376553334 + WOOD_PRODUCTS = 58440550086834602 + PAPER_PRODUCTS = 30106839080444358 + PRINTING_PUBLISHING = 25766336681098389 + PETROLEUM_REFINING = 12331083953323969 + RUBBER = 36469751685735891 + BASIC_METALS = 32453344048876642 + METAL_PRODUCTS = 1123534346391630 + MACHINERY = 11451389074113298 + ELECTRICAL_DEVICES = 33878047680249697 + COMMUNICATION_DEVICES = 24733701189547084 + MEDICAL_INSTRUMENTS = 61848754958448778 + AUTOMOBILES = 20213770409093165 + TRANSPORTATION = 58231368623465359 + FURNITURE = 29331053506731535 + SUGAR = 21948907150049163 + DIVERSIFIED_INDUSTRIES = 40355846462826897 + UTILITIES_WATER_ELECTRICITY_GAS = 54843635503648458 + FOOD_EXCEPT_SUGAR = 15508900928481581 + PHARMACEUTICALS = 3615666621538524 + CHEMICALS = 33626672012415176 + CONSTRUCTION = 41934470778361119 + RETAIL_EXCEPT_VEHICLES = 65986638607018835 + CERAMICS = 57616105980228781 + CEMENT = 70077233737515808 + NON_METALLIC_MINERALS = 14651627750314021 + INVESTMENTS = 34295935482222451 + BANKS = 72002976013856737 + OTHER_FINANCIALS = 25163959460949732 + TRANSPORTATION_2 = 24187097921483699 + RADIO_COMMUNICATION = 41867092385281437 + FINANCIAL = 61247168213690670 + FINANCIAL_MARKET_MANAGEMENT = 61985386521682984 + MASS_CONSTRUCTION = 4654922806626448 + COMPUTERS = 8900726085939949 + INFORMATION_COMMUNICATIONS = 18780171241610744 + ENGINEERING_TECHNICAL = 47233872677452574 + OIL_EXTRACTION = 65675836323214668 + INSURANCE_RETIREMENT = 59105676994811497 + TOP_30_COMPANIES_INDEX = 10523825119011581 + FLOATING_INDEX = 49579049405614711 + PRIMARY_MARKET_INDEX = 62752761908615603 + SECONDARY_MARKET_INDEX = 71704845530629737 + INDUSTRIAL_INDEX = 43754960038275285 + PRICE_INDEX_EQUAL_WEIGHT = 8384385859414435 + PRICE_INDEX_TOP_50_COMPANIES = 69932667409721265 + PRICE_INDEX_WEIGHTED_VALUE = 5798407779416661 + TOTAL_INDEX = 32097828799138957 + TOTAL_INDEX_EQUAL_WEIGHT = 67130298613737946 + TOP_50_ACTIVE_COMPANIES_INDEX = 46342955726788357 + + +# http://cdn.tsetmc.com/api/Index/GetIndexB2History/32097828799138957 +# http://cdn.tsetmc.com/api/ClosingPrice/GetIndexCompany/32097828799138957 +# http://cdn.tsetmc.com/api/ClosingPrice/GetIndexCompany/32097828799138957 +class IndexDetailsAPI(InstrumentDetailAPI): + async def get_index_history(self) -> List[IndexHistoryItem]: + url = f"{self.cdn_url}/api/Index/GetIndexB2History/{self.ins_code}" + data = await self._fetch(url) + print(data.keys()) + return [IndexHistoryItem(**i) for i in data["indexB2"]] + + async def get_index_companies(self): + url = f"{self.cdn_url}/api/ClosingPrice/GetIndexCompany/{self.ins_code}" + data = await self._fetch(url) + return data + + +async def main(): + import requests + + r = requests.get( + "http://cdn.tsetmc.com/api/Index/GetIndexB1LastAll/All/1", + headers={"User-Agent": "Mozilla/5.0"}, + ) + body = r.json() + indices = body["indexB1"] + for i in indices: + ins_code = i["insCode"] + print(f'{i["lVal30"]} = {ins_code}') + # deteail = IndexDetails(ins_code) + # info = await deteail.get_instrument_info() + # breakpoint() + + +if __name__ == "__main__": + import asyncio + + asyncio.run(main()) diff --git a/tehran_stocks/download/market.py b/tehran_stocks/download/market.py new file mode 100644 index 0000000..a596633 --- /dev/null +++ b/tehran_stocks/download/market.py @@ -0,0 +1,56 @@ +# Write a class that get data from different apis and returns data as a dataclass +# I will provide list of apis, create functions to get data from those apis +# then I will provide json schema for each api, create functions to validate data +# then use the schema to create dataclass +# then use the dataclass to return data +# everything is async, everything should handle errors +from enum import Enum +from typing import List +from .base import NEW_BASE_URL, CDN_URL, FetchMixin +from tehran_stocks.schema.market import ( + TradeTopItem, + SelectedIndexItem, + InstrumentEffectItem, + MarketOverview, +) + + +# http://cdn.tsetmc.com/api/ClosingPrice/GetTradeTop/MostVisited/1/7 +# http://cdn.tsetmc.com/api/ClosingPrice/GetTradeTop/MostVisited/2/7 +# http://cdn.tsetmc.com/api/Index/GetIndexB1LastAll/SelectedIndexes/1 +# http://cdn.tsetmc.com/api/Index/GetIndexB1LastAll/SelectedIndexes/2 +# http://cdn.tsetmc.com/api/Index/GetInstEffect/0/1/7 +# http://cdn.tsetmc.com/api/Index/GetInstEffect/0/2/7 +# http://cdn.tsetmc.com/api/MarketData/GetMarketOverview/1 +# http://cdn.tsetmc.com/api/MarketData/GetMarketOverview/2 +class MarketType(Enum): + BOURSE = 1 + FARA_BOURSE = 2 + + +class MarketAPI(FetchMixin): + def __init__(self, market: MarketType = MarketType.BOURSE) -> None: + self.session = None + self.base_url = NEW_BASE_URL + self.cdn_url = CDN_URL + self.market = market + + async def get_most_visited(self) -> List[TradeTopItem]: + url = f"{self.cdn_url}/api/ClosingPrice/GetTradeTop/MostVisited/{self.market.value}/7" + data = await self._fetch(url) + return [TradeTopItem(**d) for d in data["tradeTop"]] + + async def get_selected_indexes(self) -> List[SelectedIndexItem]: + url = f"{self.cdn_url}/api/Index/GetIndexB1LastAll/SelectedIndexes/{self.market.value}" + data = await self._fetch(url) + return [SelectedIndexItem(**d) for d in data["indexB1LastAll"]] + + async def get_instrument_effect(self) -> List[InstrumentEffectItem]: + url = f"{self.cdn_url}/api/Index/GetInstEffect/0/{self.market.value}/7" + data = await self._fetch(url) + return [InstrumentEffectItem(**d) for d in data["instEffect"]] + + async def get_market_overview(self) -> MarketOverview: + url = f"{self.cdn_url}/api/MarketData/GetMarketOverview/{self.market.value}" + data = await self._fetch(url) + return MarketOverview(**data["marketOverview"]) diff --git a/src/tehran_stocks/download/names.py b/tehran_stocks/download/names.py similarity index 54% rename from src/tehran_stocks/download/names.py rename to tehran_stocks/download/names.py index 1367e0f..524c093 100644 --- a/src/tehran_stocks/download/names.py +++ b/tehran_stocks/download/names.py @@ -1,16 +1,23 @@ -from asyncio import tasks -import requests +from typing import Optional import re -import time -import tehran_stocks.config as db -from tehran_stocks.models import Stocks -from .base import BASE_URL +from tehran_stocks.models import Instrument +from .base import BASE_URL, FetchMixin +from csv import reader -def get_stock_ids(): - url = f"{BASE_URL}/tsev2/data/MarketWatchPlus.aspx" - r = requests.get(url) - ids = set(re.findall(r"\d{15,20}", r.text)) - return list(ids) +import requests + + +class InstrumentList(FetchMixin): + @classmethod + async def get_ins_codes(cls) -> list[tuple[int, str]]: + url = f"{cls.old_base_url}/tsev2/data/MarketWatchPlus.aspx" + res = await cls._fetch_raw(url) + # '164726,1186086,579439@@40456523152045946,IRO9AGAH0371,ضترو1100,اختيارخ ص آگاه-7500-02/11/15,61031,0,3750,3750,0,0,0,0,0,3750,,1,2079,3,68,500000.00,1.00,1000,311,,5;68267250263823360,IRO9AGAH0381,ضترو' + data = reader(res.split("@@")[-1].split(";"), delimiter=",") + ids = [] + for row in data: + ids.append((int(row[0]), row[1])) + return ids def get_stock_groups(): @@ -23,18 +30,21 @@ def get_stock_groups(): def create_or_update_stock_from_dict(stock_id, stock): - if exist := Stocks.query.filter_by(code=stock_id).first(): + stock_obj = Instrument.query.filter_by(code=stock_id).first() + if stock_obj: print(f"stock with code {stock_id} exist") - exist.shareCount = stock["shareCount"] - exist.baseVol = stock["baseVol"] - exist.sectorPe = stock["sectorPe"] - exist.estimatedEps = stock["estimatedEps"] + stock_obj.shareCount = stock["shareCount"] + stock_obj.baseVol = stock["baseVol"] + stock_obj.sectorPe = stock["sectorPe"] + stock_obj.estimatedEps = stock["estimatedEps"] else: + stock_obj = Instrument(**stock) print(f"creating stock with code {stock_id}") - db.session.add(Stocks(**stock)) + # db.session.add(stock_obj) + return stock_obj -def get_stock_detail(stock_id: str) -> Stocks: +def get_stock_detail(stock_id: str) -> Optional[Instrument]: """ Dowload stocks detail and save them to the database. better not use it alone. @@ -59,51 +69,51 @@ def get_stock_detail(stock_id: str) -> Stocks: "instId": re.findall(r"InstrumentID='([\w\d]*)|$',", r.text)[0], } except IndexError: - return + return None - stock["insCode"] = ( - stock_id if re.findall(r"InsCode='(\d*)',", r.text)[0] == stock_id else 0 + stock["ins_code"] = ( + stock_id if re.findall(r"ins_code='(\d*)',", r.text)[0] == stock_id else 0 ) stock["baseVol"] = float(re.findall(r"BaseVol=([\.\d]*),", r.text)[0]) try: stock["name"] = re.findall(r"LVal18AFC='([\D]*)',", r.text)[0] - except: - return + except Exception: + return None try: stock["group_name"] = re.findall(r"LSecVal='([\D]*)',", r.text)[0] - except: - return + except Exception: + return None try: stock["title"] = re.findall(r"Title='([\D]*)',", r.text)[0] - except: - return + except Exception: + return None try: stock["sectorPe"] = float(re.findall(r"SectorPE='([\.\d]*)',", r.text)[0]) - except: + except Exception: stock["sectorPe"] = None try: stock["shareCount"] = float(re.findall(r"ZTitad=([\.\d]*),", r.text)[0]) - except: + except Exception: stock["shareCount"] = None try: stock["estimatedEps"] = float( re.findall(r"EstimatedEPS='([\.\d]*)',", r.text)[0] ) - except: + except Exception: stock["estimatedEps"] = None stock["group_code"] = re.findall(r"CSecVal='([\w\d]*)|$',", r.text)[0] if stock["name"] == "',DEven='',LSecVal='',CgrValCot='',Flow='',InstrumentID='": - return False + return None - create_or_update_stock_from_dict(stock_id, stock) + stock_obj = create_or_update_stock_from_dict(stock_id, stock) - try: - db.session.commit() - except: - print(f"stock with code {stock_id} exist") - db.session.rollback() - return stock + # try: + # db.session.commit() + # except Exception: + # print(f"stock with code {stock_id} exist") + # db.session.rollback() + return stock_obj def fill_stock_table(): @@ -115,6 +125,7 @@ def fill_stock_table(): 4- save them to database 5- guides you to use the package """ + pass # URL = "https://ts-api.ir/api/stocks" # try: # print("try Downloading stock table from the package api... ") @@ -137,17 +148,17 @@ def fill_stock_table(): # except Exception as e: # print(e) # pass - print("Downloading group ids...") - stocks = get_stock_ids() - for i, stock in enumerate(stocks): - get_stock_detail(stock) - print( - f"downloading stocks details, changes: {(i+1)/len(stocks)*100:.1f}% completed", - end="\r", - ) - - print("Add all groups, you can download stock price by following codes") - print("from tehran_stocks import downloader") - print(" downloader.download_all() # for downloading all data") - print("downloader.download_group(group_id) # to download specefic group data") - print("downloader.download_stock(stock) to downloand stock specefic") + # print("Downloading group ids...") + # # stocks = get_stock_ids() + # for i, stock in enumerate(stocks): + # get_stock_detail(stock) + # print( + # f"downloading stocks details, changes: {(i+1)/len(stocks)*100:.1f}% completed", + # end="\r", + # ) + + # print("Add all groups, you can download stock price by following codes") + # print("from tehran_stocks import downloader") + # print(" downloader.download_all() # for downloading all data") + # print("downloader.download_group(group_id) # to download specefic group data") + # print("downloader.download_stock(stock) to downloand stock specefic") diff --git a/tehran_stocks/download/price.py b/tehran_stocks/download/price.py new file mode 100644 index 0000000..47e3595 --- /dev/null +++ b/tehran_stocks/download/price.py @@ -0,0 +1,78 @@ +import io +from datetime import datetime +from typing import List, Optional + +import pandas as pd +from jdatetime import date as jdate + +from tehran_stocks.schema.price import PriceAdjustItem + +from .base import BASE_URL, FetchMixin + + +def convert_to_shamsi(date): + date = str(date) + return jdate.fromgregorian( + day=int(date[-2:]), month=int(date[4:6]), year=int(date[:4]) + ).strftime("%Y/%m/%d") + + +class InstrumentPriceHistory(FetchMixin): + def __init__(self, ins_code: int) -> None: + super().__init__() + self.ins_code = ins_code + + async def get_stock_price_history( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + jalali: bool = True, + ) -> pd.DataFrame: + """Get stock price history from the web. + + params: + ---------------- + stock_id: int + http://www.old.tsetmc.com/Loader.aspx?ParTree=151311&i=**35700344742885862#** + number after i= + + return: + ---------------- + pd.DataFrame + date: str + open: float + high: float + low: float + close: float + volume: int + + example + ---------------- + df = get_stock_price_history(35700344742885862) + """ + # async def _fetch(self, url: str) -> Dict[str, Any]: + + url = f"{BASE_URL}/tse/data/Export-txt.aspx?a=InsTrade&InsCode={self.ins_code}" + if start_date is None: + start_date = "20000101" + # url += f"&DateFrom={start_date}" + if end_date is None: + end_date = datetime.now().strftime("%Y%m%d") + # url += f"&DateTo={end_date}" + + url += f"&DateFrom={start_date}&DateTo={end_date}&b=0" + + s = await self._fetch_raw(url) + df = pd.read_csv(io.StringIO(s)) + df.columns = [i[1:-1].lower() for i in df.columns] + if "ticker" not in df.columns or "close" not in df.columns: + return pd.DataFrame() + if jalali: + df["date_shamsi"] = df["dtyyyymmdd"].apply(convert_to_shamsi) + return df + + async def get_price_adjusted(self) -> List[PriceAdjustItem]: + data = await self._fetch( + f"{self.cdn_url}/api/ClosingPrice/GetPriceAdjustList/{self.ins_code}" + ) + return [PriceAdjustItem(**i) for i in data["priceAdjust"]] diff --git a/tehran_stocks/initializer.py b/tehran_stocks/initializer.py new file mode 100644 index 0000000..1698238 --- /dev/null +++ b/tehran_stocks/initializer.py @@ -0,0 +1,165 @@ +""" +This module is uses to manage flow of tehran_stocks package. +It will handle config and database creation and filling. +""" + + +import asyncio +from time import time +from typing import List +from tehran_stocks import models +from tehran_stocks.data.instrument_types import basic_instrument_types +from tehran_stocks.download.names import InstrumentList +from tehran_stocks.download.details import InstrumentDetailAPI +from tehran_stocks.config import config_file, engine as engine_config +from tehran_stocks.config.engine import get_session +from sqlalchemy import inspect +from tqdm import tqdm +from tehran_stocks.models.instrument import Instrument + +from tehran_stocks.schema.details import InstrumentInfo + + +def create_config(): + """ + Check if config.yml exists in package folder. + if not, it will create one. + """ + config_file.create_tse_folder() + config_file.create_config() + + +def check_database(engine): + """ + Check if database exists. + if not, it will create one. + """ + if not engine.dialect.has_table(engine, "stocks"): + print("Database not found") + print("Creating database...") + models.create_database(engine) + print("Done!") + + +def create_engine(): + database_config = config_file.get_database_config() + engine_uri = engine_config.create_engine_uri(database_config) + engine = engine_config.create_engine(engine_uri) + return engine + + +def create_database(engine): + if not inspect(engine).has_table(models.InstrumentPrice.__tablename__): + print("Database not found") + print("Creating database...") + models.create_database(engine) + + +async def get_all_price() -> None: + engine = create_engine() + session = get_session(engine=engine) + instruments: list[Instrument] = session.query(models.Instrument).all() + tasks = [] + for instrument in instruments: + tasks.append(asyncio.create_task(instrument._get_update_price(save=False))) + # run in batch of 100 + batch_size = 20 + t0 = time() + failed_tasks = 0 + finished_tasks = 0 + for i in tqdm(range(0, len(tasks), batch_size)): + result = await asyncio.gather( + *tasks[i : i + batch_size], return_exceptions=True + ) + # save to database + for df in result: + if isinstance(df, Exception): + print(df) + failed_tasks += 1 + continue + + df.to_sql("instrument_price", engine, if_exists="append", index=False) + finished_tasks += 1 + tqdm.write(f"{i}/{len(tasks)} completed", end="\r") + + print(f"Done in {time()-t0:.1f} seconds") + print(f"{failed_tasks} failed, and {finished_tasks} success") + + # await task + + +async def fill_db() -> None: + print("Downloading instruments name and details from TSETMC") + print("may take few minutes") + t0 = time() + + ins_ids = await InstrumentList.get_ins_codes() + tasks = [] + for ins_code, ins_id in ins_ids: + if ins_id[2:4] in basic_instrument_types: + tasks.append( + asyncio.create_task(InstrumentDetailAPI(ins_code).get_instrument_info()) + ) + print(f"Creating {len(tasks)} tasks in {time()-t0:.1f} seconds") + print("Start downloading details, it may take few minutes") + batch_size = 100 + t0 = time() + results: List[InstrumentInfo] = [] + tasks = tasks[:400] + for i in tqdm(range(0, len(tasks), batch_size)): + # await asyncio.gather(*tasks[i:i+batch_size]) + results += await asyncio.gather( + *tasks[i : i + batch_size], return_exceptions=True + ) + tqdm.write(f"{i}/{len(tasks)} completed", end="\r") + print(f"Done in {time()-t0:.1f} seconds") + failed_tasks = [i for i in results if isinstance(i, Exception)] + + # print(f"{len([i for i in results if isinstance(i, Exception)])} failed, and + print(f"{len(failed_tasks)} failed, and {len(results)-len(failed_tasks)} success") + + engine = create_engine() + session = get_session(engine=engine) + print("Adding to database") + t0 = time() + for result in tqdm(results): + if isinstance(result, InstrumentInfo): + item = models.Instrument.from_dict(result) + session.add(item) + try: + session.commit() + except Exception as e: + print(e) + session.rollback() + print(f"Done in {time()-t0:.1f} seconds") + + # check if all instruments are added to database + n_in_db = session.execute("select count(*) from instruments").scalar() + # number of failed tasks + + # breakpoint() + + # # print("Stock table is available now, example:") + # # print("from tehran_stocks import Instrument") + # # print('stock =Instrument.query.filter_by(name="کگل").first()') + + # a = input("Do you want to download all price? [y,(n)]") + # if a == "y": + # print("Downloading price:") + # await get_all_price() + # # else: + # print("if you want download all prices use tehran_stocks.get_all_price() ") + # print("if you want download price history of a specfic stock use: ") + # print("stock.update()") + # print("or use tehran_stocks.update_group(id) ") + # print("For more info go to:") + # print("https://github.com/ghodsizadeh/tehran-stocks") + + +if __name__ == "__main__": + create_config() + engine = create_engine() + create_database(engine) + # asyncio.run(fill_db()) + asyncio.run(get_all_price()) + print("Done!") diff --git a/tehran_stocks/models/__init__.py b/tehran_stocks/models/__init__.py new file mode 100644 index 0000000..e66c394 --- /dev/null +++ b/tehran_stocks/models/__init__.py @@ -0,0 +1,9 @@ +from .create import create_database +from .instrument import Instrument +from .instrument_price import InstrumentPrice + +__all__ = [ + "Instrument", + "InstrumentPrice", + "create_database", +] diff --git a/tehran_stocks/models/create.py b/tehran_stocks/models/create.py new file mode 100644 index 0000000..0679e21 --- /dev/null +++ b/tehran_stocks/models/create.py @@ -0,0 +1,7 @@ +from .instrument import Instrument +from .instrument_price import InstrumentPrice + + +def create_database(engine): + Instrument.__table__.create(engine) + InstrumentPrice.__table__.create(engine) diff --git a/tehran_stocks/models/instrument.py b/tehran_stocks/models/instrument.py new file mode 100644 index 0000000..44bada5 --- /dev/null +++ b/tehran_stocks/models/instrument.py @@ -0,0 +1,179 @@ +from typing import List, Optional, TYPE_CHECKING + +import pandas as pd +import requests +from sqlalchemy import Column, Float, Integer, String +from sqlalchemy.orm import relationship + +from tehran_stocks.config import Base +from tehran_stocks.config import engine +from tehran_stocks.config.engine import get_session +from tehran_stocks.data.instrument_types import InstrumentType +from tehran_stocks.download.base import BASE_URL +from tehran_stocks.download.details import InstrumentDetailAPI +from tehran_stocks.schema.details import InstrumentInfo, ShareHolderItem +from tehran_stocks.download.price import InstrumentPriceHistory + +if TYPE_CHECKING: + pass + + +class Instrument(Base): + __tablename__ = "instruments" + + id = Column(Integer, primary_key=True) + name = Column(String, index=True) + full_name = Column(String, index=True) + full_name_en = Column(String) + sector_name = Column(String) + sector_code = Column(Integer, index=True) + ins_id = Column(String, unique=True) + ins_code = Column(String, index=True, unique=True) + shareCount = Column(Float) + estimatedEps = Column(Float) + baseVol = Column(Float) + type = Column(String, index=True) # like stock, etf, fund, index + prices = relationship("InstrumentPrice", backref="instrument", lazy="dynamic") + + @classmethod + def from_dict( + cls, + data: InstrumentInfo, + type: str = InstrumentType.Stock_Exchange_Stocks.value, + ): + return cls( + name=data.name, + full_name=data.full_name, + full_name_en=data.full_name_en, + sector_name=data.sector and data.sector.sector_name, + sector_code=data.sector and data.sector.sector_code, + ins_id=data.ins_id, + ins_code=data.ins_code, + shareCount=data.share_count, + estimatedEps=data.eps and data.eps.estimated_eps, + baseVol=data.base_vol, + type=type, + ) + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + self.details = InstrumentDetailAPI(self.ins_code) + self.market_api = True + self._cached = False + self._dfcounter = 0 + self._df: pd.DataFrame = pd.DataFrame() + + async def share_holders(self, date: Optional[str] = None) -> List[ShareHolderItem]: + return await self.details.get_share_holder(date) + + @property + def df(self) -> pd.DataFrame: + """dataframe of stock price with date and OHLC""" + + return pd.DataFrame() + + async def _get_update_price(self, save: bool = True) -> pd.DataFrame: + from tehran_stocks.models.instrument_price import InstrumentPrice + + api = InstrumentPriceHistory(self.ins_code) + session = get_session() + # last_exist_date = ( + # self.prices.order_by(InstrumentPrice.date.desc()) + # .first() + # .date + # .strftime("%Y%m%d") + # ) + last_price = ( + session.query(InstrumentPrice) + .filter(InstrumentPrice.ins_code == self.ins_code) + .order_by(InstrumentPrice.date.desc()) + .first() + ) + if last_price: + last_exist_date = f"{last_price.date}" + else: + last_exist_date = None + data = await api.get_stock_price_history(start_date=last_exist_date) + data["ins_code"] = self.ins_code + if save: + data.to_sql("instrument_price", engine, if_exists="append", index=False) + return data + + def get_dividend(self) -> pd.DataFrame: + """get changes in price for dividend and changes in share + postive value is dividend and negative value is changes in share + + Returns: + pd.DataFrame: _description_ + """ + + url = f"{BASE_URL}/Loader.aspx?Partree=15131G&i={self.code}" + r = requests.get(url) + changes = pd.read_html(r.text)[0] + changes.columns = ["date", "after", "before"] + changes["dividend"] = changes.before - changes.after + changes["date"] = changes.date.jalali.parse_jalali("%Y/%m/%d") + changes["gdate"] = changes.date.jalali.to_gregorian() + return changes + + def get_shares_history(self) -> pd.DataFrame: + """_summary_get changes in shares + + Returns: + pd.DataFrame: return day of shares changes and shares count [date, new_shares, old_shares, gdate] + """ + url = f"{BASE_URL}/Loader.aspx?Partree=15131H&i={self.code}" + r = requests.get(url) + df = pd.read_html(r.text)[0] + df.columns = ["date", "new_shares", "old_shares"] + df["date"] = df.date.jalali.parse_jalali("%Y/%m/%d") + df["gdate"] = df.date.jalali.to_gregorian() + return df + + @property + def mpl(self): + self._mpl = self.df.rename( + columns={ + "close": "Close", + "open": "Open", + "high": "High", + "low": "Low", + "vol": "Volume", + } + ) + return self._mpl + + def summary(self): + """summart of stock""" + df = self.df + sdate = df.index.min().strftime("%Y/%m/%d") + edate = df.index.max().strftime("%Y/%m/%d") + + print(f"Start date: {sdate}") + print(f"End date: {edate}") + print(f"Total days: {len(df)}") + + async def get_instant_detail(self) -> InstrumentInfo: + """get instant detail of stock + last_price, last_close, last_open, last_high, last_low, last_vol, trade_count, trade_value,market_cap + instantly from the website + + Returns: + dict: { last_price, last_close, last_open, last_high, last_low, last_vol, trade_count, trade_value,market_cap} + """ + return await self.details.get_instrument_info() + + def __repr__(self): + return f"{self.title}-{self.name}-{self.group_name}" + + def __str__(self): + return self.name + + @staticmethod + def get_group(): + session = get_session(engine) + return ( + session.query(Instrument.group_code, Instrument.group_name) + .group_by(Instrument.group_code) + .all() + ) diff --git a/tehran_stocks/models/instrument_price.py b/tehran_stocks/models/instrument_price.py new file mode 100644 index 0000000..6d8d6b9 --- /dev/null +++ b/tehran_stocks/models/instrument_price.py @@ -0,0 +1,39 @@ +from sqlalchemy import ( + BIGINT, + Column, + Float, + ForeignKey, + Integer, + String, + UniqueConstraint, +) + +from tehran_stocks.config import Base + + +class InstrumentPrice(Base): + __tablename__ = "instrument_price" + + id = Column(Integer, primary_key=True) + ins_code = Column( + String, ForeignKey("instruments.ins_code"), index=True, nullable=False + ) + ticker = Column(String) + date = Column("dtyyyymmdd", Integer, index=True) + date_shamsi = Column(String) + first = Column(Float) + high = Column(Float) + low = Column(Float) + close = Column(Float) + value = Column(BIGINT) + vol = Column(BIGINT) + openint = Column(Integer) + per = Column(String) + open = Column(Float) + last = Column(Float) + + # add ins_code date unique constraint + __table_args__ = (UniqueConstraint(ins_code, date, name="_ins_code_date_uc"),) + + def __repr__(self): + return f"{self.stock.name}, {self.date}, {self.close:.0f}" diff --git a/tehran_stocks/schema/__init__.py b/tehran_stocks/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tehran_stocks/schema/details.py b/tehran_stocks/schema/details.py new file mode 100644 index 0000000..7b2f365 --- /dev/null +++ b/tehran_stocks/schema/details.py @@ -0,0 +1,165 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class Eps(BaseModel): + eps_value: Optional[float] = Field(default=None, alias="epsValue") + estimated_eps: Optional[float] = Field(default=None, alias="estimatedEPS") + sector_pe: Optional[float] = Field(default=None, alias="sectorPE") + psr: Optional[float] = Field(default=None, alias="psr") + + +class Sector(BaseModel): + d_even: Optional[int] = Field(default=None, alias="dEven") + sector_code: Optional[str] = Field(default=None, alias="cSecVal") + sector_name: Optional[str] = Field(default=None, alias="lSecVal") + + +class StaticThreshold(BaseModel): + ins_code: Optional[str] = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + h_even: Optional[int] = Field(default=None, alias="hEven") + max_price: Optional[float] = Field(default=None, alias="psGelStaMax") + min_price: Optional[float] = Field(default=None, alias="psGelStaMin") + + +class InstrumentInfo(BaseModel): + base_vol: Optional[int] = Field(default=None, alias="baseVol") + company_id: Optional[str] = Field(default=None, alias="cIsin") + ins_id: Optional[str] = Field(default=None, alias="instrumentID") + min_week: Optional[float] = Field(default=None, alias="minWeek") + max_week: Optional[float] = Field(default=None, alias="maxWeek") + min_year: Optional[float] = Field(default=None, alias="minYear") + max_year: Optional[float] = Field(default=None, alias="maxYear") + average_monthly_volume: Optional[float] = Field(default=None, alias="qTotTran5JAvg") + d_even: Optional[int] = Field(default=None, alias="dEven") + top_inst: Optional[int] = Field(default=None, alias="topInst") + fara_desc: Optional[str] = Field(default=None, alias="faraDesc") + contract_size: Optional[int] = Field(default=None, alias="contractSize") + nav: Optional[float] + under_supervision: Optional[int] = Field(default=None, alias="underSupervision") + c_val_mne: Optional[str] = Field(default=None, alias="cValMne") + ins_code: Optional[str] = Field(default=None, alias="insCode") + full_name_en: Optional[str] = Field(default=None, alias="lVal18") + full_name: Optional[str] = Field(default=None, alias="lVal30") + name: Optional[str] = Field(default=None, alias="lVal18AFC") + market_type: Optional[int] = Field(default=None, alias="flow") + market_title: Optional[str] = Field(default=None, alias="flowTitle") + market_long_title: Optional[str] = Field(default=None, alias="cgrValCot") + share_count: Optional[int] = Field(default=None, alias="zTitad") + eps: Optional[Eps] + sector: Optional[Sector] + + +class InstrumentState(BaseModel): + idn: Optional[int] + d_even: Optional[int] = Field(default=None, alias="dEven") + h_even: Optional[int] = Field(default=None, alias="hEven") + ins_code: Optional[str] = Field(default=None, alias="insCode") + full_name: Optional[str] = Field(default=None, alias="lVal30") + name: Optional[str] = Field(default=None, alias="lVal18AFC") + c_etaval: Optional[str] = Field(default=None, alias="cEtaval") + real_heven: Optional[int] = Field(default=None, alias="realHeven") + under_supervision: Optional[int] = Field(default=None, alias="underSupervision") + c_etaval_title: Optional[str] = Field(default=None, alias="cEtavalTitle") + + +class TradeClientType(BaseModel): + buy_volume_individual: Optional[float] = Field(default=None, alias="buy_I_Volume") + buy_volume_legal: Optional[float] = Field(default=None, alias="buy_N_Volume") + buy_volume_legal_foreign: Optional[float] = Field( + default=None, alias="buy_DDD_Volume" + ) + buy_count_individual: Optional[int] = Field(default=None, alias="buy_CountI") + buy_count_legal: Optional[int] = Field(default=None, alias="buy_CountN") + buy_count_legal_foreign: Optional[int] = Field(default=None, alias="buy_CountDDD") + sell_volume_individual: Optional[float] = Field(default=None, alias="sell_I_Volume") + sell_volume_legal: Optional[float] = Field(default=None, alias="sell_N_Volume") + sell_count_individual: Optional[int] = Field(default=None, alias="sell_CountI") + sell_count_legal: Optional[int] = Field(default=None, alias="sell_CountN") + + +class Trade(BaseModel): + ins_code: Optional[str] = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + n_tran: Optional[int] = Field(default=None, alias="nTran") + h_even: Optional[int] = Field(default=None, alias="hEven") + volume: Optional[int] = Field(default=None, alias="qTitTran") + price: Optional[float] = Field(default=None, alias="pTran") + volume_ng: Optional[int] = Field(default=None, alias="qTitNgJ") + i_sens_varp: Optional[str] = Field(default=None, alias="iSensVarP") + p_ph_sea_cotj: Optional[float] = Field(default=None, alias="pPhSeaCotJ") + p_pb_sea_cotj: Optional[float] = Field(default=None, alias="pPbSeaCotJ") + i_anu_tran: Optional[int] = Field(default=None, alias="iAnuTran") + xq_var_pjdr_prf: Optional[float] = Field(default=None, alias="xqVarPJDrPRf") + canceled: Optional[int] + + +class ClosingPriceData(BaseModel): + instrument_state: InstrumentState = Field(default=None, alias="instrumentState") + instrument: Optional[InstrumentInfo] + last_h_even: Optional[int] = Field(default=None, alias="lastHEven") + final_last_date: Optional[int] = Field(default=None, alias="finalLastDate") + nvt: Optional[float] + mop: Optional[int] + thirty_day_closing_history: Optional[str] = Field( + default=None, alias="thirtyDayClosingHistory" + ) + price_change: Optional[float] = Field(default=None, alias="priceChange") + price_min: Optional[float] = Field(default=None, alias="priceMin") + price_max: Optional[float] = Field(default=None, alias="priceMax") + price_yesterday: Optional[float] = Field(default=None, alias="priceYesterday") + price_first: Optional[float] = Field(default=None, alias="priceFirst") + last: Optional[bool] + id: Optional[int] + ins_code: Optional[str] = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + h_even: Optional[int] = Field(default=None, alias="hEven") + p_closing: Optional[float] = Field(default=None, alias="pClosing") + i_close: Optional[bool] = Field(default=None, alias="iClose") + y_close: Optional[bool] = Field(default=None, alias="yClose") + p_dr_cot_val: Optional[float] = Field(default=None, alias="pDrCotVal") + z_tot_tran: Optional[float] = Field(default=None, alias="zTotTran") + q_tot_tran_5j: Optional[float] = Field(default=None, alias="qTotTran5J") + q_tot_cap: Optional[float] = Field(default=None, alias="qTotCap") + + +class BestLimit(BaseModel): + number: Optional[int] + volume_buy: Optional[int] = Field(default=None, alias="qTitMeDem") + count_buy: Optional[int] = Field(default=None, alias="zOrdMeDem") + price_buy: Optional[float] = Field(default=None, alias="pMeDem") + price_sell: Optional[float] = Field(default=None, alias="pMeOf") + count_sell: Optional[int] = Field(default=None, alias="zOrdMeOf") + volume_sell: Optional[int] = Field(default=None, alias="qTitMeOf") + ins_code: Optional[str] = Field(default=None, alias="insCode") + + +class BestLimitHistory(BaseModel): + idn: Optional[int] + d_even: Optional[int] = Field(default=None, alias="dEven") + h_even: Optional[int] = Field(default=None, alias="hEven") + ref_id: Optional[int] = Field(default=None, alias="refID") + number: Optional[int] + volume_buy: Optional[int] = Field(default=None, alias="qTitMeDem") + count_buy: Optional[int] = Field(default=None, alias="zOrdMeDem") + price_buy: Optional[float] = Field(default=None, alias="pMeDem") + price_sell: Optional[float] = Field(default=None, alias="pMeOf") + count_sell: Optional[int] = Field(default=None, alias="zOrdMeOf") + volume_sell: Optional[int] = Field(default=None, alias="qTitMeOf") + ins_code: Optional[str] = Field(default=None, alias="insCode") + + +class ShareHolderItem(BaseModel): + share_holder_id: Optional[int] = Field(default=None, alias="shareHolderID") + share_holder_name: Optional[str] = Field(default=None, alias="shareHolderName") + company_id: Optional[str] = Field(default=None, alias="cIsin") + d_even: Optional[int] = Field(default=None, alias="dEven") + number_of_shares: Optional[float] = Field(default=None, alias="numberOfShares") + per_of_shares: Optional[float] = Field(default=None, alias="perOfShares") + change: Optional[int] = Field(default=None, alias="change") + change_amount: Optional[float] = Field(default=None, alias="changeAmount") + share_holder_share_id: Optional[int] = Field( + default=None, alias="shareHolderShareID" + ) diff --git a/tehran_stocks/schema/index.py b/tehran_stocks/schema/index.py new file mode 100644 index 0000000..71cdcc9 --- /dev/null +++ b/tehran_stocks/schema/index.py @@ -0,0 +1,16 @@ +from pydantic import BaseModel, Field +from typing import Optional + + +class IndexHistoryItem(BaseModel): + ins_code: Optional[int] = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + x_niv_inu_cl_mres_ibs: Optional[float] = Field( + default=None, alias="xNivInuClMresIbs" + ) + x_niv_inu_pb_mres_ibs: Optional[float] = Field( + default=None, alias="xNivInuPbMresIbs" + ) + x_niv_inu_ph_mres_ibs: Optional[float] = Field( + default=None, alias="xNivInuPhMresIbs" + ) diff --git a/tehran_stocks/schema/market.py b/tehran_stocks/schema/market.py new file mode 100644 index 0000000..fa95782 --- /dev/null +++ b/tehran_stocks/schema/market.py @@ -0,0 +1,91 @@ +from pydantic import BaseModel, Field +from typing import Optional + +from tehran_stocks.schema.details import InstrumentInfo, InstrumentState + + +class TradeTopItem(BaseModel): + instrumentState: Optional[InstrumentState] = Field( + default=None, alias="instrumentState" + ) + instrument: Optional[InstrumentInfo] = Field(default=None, alias="instrument") + last_h_even: Optional[int] = Field(default=None, alias="lastHEven") + final_last_date: Optional[int] = Field(default=None, alias="finalLastDate") + nvt: Optional[float] + mop: Optional[int] + thirty_day_closing_history: Optional[str] = Field( + default=None, alias="thirtyDayClosingHistory" + ) + price_change: Optional[float] = Field(default=None, alias="priceChange") + price_min: Optional[float] = Field(default=None, alias="priceMin") + price_max: Optional[float] = Field(default=None, alias="priceMax") + price_yesterday: Optional[float] = Field(default=None, alias="priceYesterday") + price_first: Optional[float] = Field(default=None, alias="priceFirst") + last: Optional[bool] + id: Optional[int] + ins_code: Optional[str] = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + h_even: Optional[int] = Field(default=None, alias="hEven") + p_closing: Optional[float] = Field(default=None, alias="pClosing") + i_close: Optional[bool] = Field(default=None, alias="iClose") + y_close: Optional[bool] = Field(default=None, alias="yClose") + p_dr_cot_val: Optional[float] = Field(default=None, alias="pDrCotVal") + z_tot_tran: Optional[float] = Field(default=None, alias="zTotTran") + q_tot_tran_5j: Optional[float] = Field(default=None, alias="qTotTran5J") + q_tot_cap: Optional[float] = Field(default=None, alias="qTotCap") + + +class SelectedIndexItem: + ins_code: str = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + h_even: Optional[int] = Field(default=None, alias="hEven") + x_dr_niv_j_idx_004: Optional[float] = Field(default=None, alias="xDrNivJIdx004") + x_ph_niv_j_idx_004: Optional[float] = Field(default=None, alias="xPhNivJIdx004") + x_pb_niv_j_idx_004: Optional[float] = Field(default=None, alias="xPbNivJIdx004") + x_var_idx_j_rf_v: Optional[float] = Field(default=None, alias="xVarIdxJRfV") + last: Optional[bool] + index_change: Optional[float] = Field(default=None, alias="indexChange") + l_val_30: Optional[str] = Field(default=None, alias="lVal30") + c1: Optional[int] + c2: Optional[int] + c3: Optional[int] + c4: Optional[int] + + +class InstrumentEffectItem(BaseModel): + ins_code: str = Field(default=None, alias="insCode") + instrument: Optional[InstrumentInfo] = Field(default=None, alias="instrument") + p_closing: Optional[float] = Field(default=None, alias="pClosing") + inst_effect_value: Optional[float] = Field(default=None, alias="instEffectValue") + + +class MarketOverview(BaseModel): + last_data_d_even: Optional[int] = Field(default=None, alias="lastDataDEven") + last_data_h_even: Optional[int] = Field(default=None, alias="lastDataHEven") + index_last_value: Optional[float] = Field(default=None, alias="indexLastValue") + index_change: Optional[float] = Field(default=None, alias="indexChange") + index_equal_weighted_last_value: Optional[float] = Field( + default=None, alias="indexEqualWeightedLastValue" + ) + index_equal_weighted_change: Optional[float] = Field( + default=None, alias="indexEqualWeightedChange" + ) + market_activity_d_even: Optional[int] = Field( + default=None, alias="marketActivityDEven" + ) + market_activity_h_even: Optional[int] = Field( + default=None, alias="marketActivityHEven" + ) + market_activity_z_tot_tran: Optional[int] = Field( + default=None, alias="marketActivityZTotTran" + ) + market_activity_q_tot_cap: Optional[float] = Field( + default=None, alias="marketActivityQTotCap" + ) + market_activity_q_tot_tran: Optional[float] = Field( + default=None, alias="marketActivityQTotTran" + ) + market_state: Optional[str] = Field(default=None, alias="marketState") + market_value: Optional[float] = Field(default=None, alias="marketValue") + market_value_base: Optional[float] = Field(default=None, alias="marketValueBase") + market_state_title: Optional[str] = Field(default=None, alias="marketStateTitle") diff --git a/tehran_stocks/schema/price.py b/tehran_stocks/schema/price.py new file mode 100644 index 0000000..33c8ce9 --- /dev/null +++ b/tehran_stocks/schema/price.py @@ -0,0 +1,14 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class PriceAdjustItem(BaseModel): + ins_code: Optional[str | int] = Field(default=None, alias="insCode") + d_even: Optional[int] = Field(default=None, alias="dEven") + p_closing: Optional[float] = Field(default=None, alias="pClosing") + p_closing_not_adjusted: Optional[float] = Field( + default=None, alias="pClosingNotAdjusted" + ) + corporate_type_code: Optional[int] = Field(default=None, alias="corporateTypeCode") + instrument: Optional[int] = Field(default=None, alias="instrument") diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..f0349ff --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,90 @@ +import os + +from pytest_mock import MockFixture +from tehran_stocks.config.config_file import ( + HOME_PATH, + TSE_FOLDER, + create_config, + create_tse_folder, +) +from tehran_stocks.config.engine import create_engine_uri +import yaml + +# path os.remove + + +def test_create_engine_uri_sqlite(): + database_config = {"engine": "sqlite", "path": "/path/to/database.db"} + expected_uri = "sqlite:////path/to/database.db" + assert create_engine_uri(database_config) == expected_uri + + +def test_create_engine_uri_postgres(): + database_config = { + "engine": "postgres", + "host": "localhost", + "port": "5432", + "database": "test_db", + "user": "test_user", + "password": "test_password", + } + expected_uri = "postgres://test_user:test_password@localhost:5432/test_db" + assert create_engine_uri(database_config) == expected_uri + + +def test_create_engine_uri_sqlite_default_path(): + database_config = {"engine": "sqlite"} + expected_uri = f"sqlite:///{HOME_PATH}/{TSE_FOLDER}/tse_data.db" + assert create_engine_uri(database_config) == expected_uri + + +def test_create_engine_uri_no_engine(): + database_config = { + "host": "localhost", + "port": "5432", + "database": "test_db", + "user": "test_user", + "password": "test_password", + } + expected_uri = f"sqlite:///{HOME_PATH}/{TSE_FOLDER}/test_db.db" + assert create_engine_uri(database_config) == expected_uri + + +def test_create_tse_folder_exists(mocker: MockFixture): + mocker.patch("os.path.join", return_value=HOME_PATH) + mocker.patch("os.mkdir", side_effect=FileExistsError) + mocker.patch("os.path.exists", return_value=True) + assert create_tse_folder() is False + os.mkdir.assert_called_once_with(HOME_PATH) # type: ignore + + +def test_create_tse_folder_not_exists(mocker): + mocker.patch("os.path.join", return_value=HOME_PATH) + mocker.patch("os.mkdir") + mocker.patch("os.path.exists", return_value=False) + + assert create_tse_folder() is True + + os.mkdir.assert_called_once_with(HOME_PATH) + + +def test_create_config_exists(mocker: MockFixture): + mocker.patch("os.path.exists", return_value=True) + mocker.patch("__main__.open", mocker.mock_open()) + mocker.patch("yaml.full_load") + mocker.patch("yaml.dump") + + create_config() + + yaml.dump.assert_not_called() # type: ignore + + +def test_create_config_not_exists(mocker): + mocker.patch("os.path.exists", return_value=False) + mocker.patch("__main__.open", mocker.mock_open()) + mocker.patch("yaml.full_load") + mocker.patch("yaml.dump") + + create_config() + + yaml.dump.assert_called() diff --git a/tests/test_data.py b/tests/test_data.py new file mode 100644 index 0000000..5d6d257 --- /dev/null +++ b/tests/test_data.py @@ -0,0 +1,15 @@ +from tehran_stocks.data.groups import IndustryGroup +from tehran_stocks.data.instrument_types import InstrumentType +from tehran_stocks.data.tickers import TickerEnum +import pytest + + +@pytest.mark.offline +def test_enums(): + assert IndustryGroup.INDEX.value == "X1" + assert IndustryGroup.INDEX.name == "INDEX" + assert IndustryGroup.INDEX.farsi_name == "شاخص" + assert InstrumentType.Stock_Exchange_Stocks.value == "O1" + assert InstrumentType.Stock_Exchange_Stocks.name == "Stock_Exchange_Stocks" + assert TickerEnum.ASP.value == 17617474823279712 + breakpoint() diff --git a/tests/test_download_details.py b/tests/test_download_details.py new file mode 100644 index 0000000..9cb65f9 --- /dev/null +++ b/tests/test_download_details.py @@ -0,0 +1,96 @@ +from tehran_stocks.download.details import InstrumentDetailAPI +from tehran_stocks.schema.details import ( + BestLimit, + BestLimitHistory, + InstrumentInfo, + InstrumentState, + ShareHolderItem, + Trade, + TradeClientType, + ClosingPriceData, +) +import pytest +from datetime import datetime + + +@pytest.fixture +def api(): + insCode = 48990026850202503 + + return InstrumentDetailAPI(insCode) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_details(api: InstrumentDetailAPI): + data = await api.get_instrument_info() + assert data is not None + assert isinstance(data, InstrumentInfo) + assert data.ins_code == "48990026850202503" + assert data.name == "خگستر" + assert (data.d_even == int(datetime.now().strftime("%Y%m%d"))) or (data.d_even == 0) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_instrument_state(api: InstrumentDetailAPI): + data = await api.get_instrument_state_top() + assert data is not None + assert isinstance(data, InstrumentState) + # assert data.ins_code == '48990026850202503' + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_client_type(api): + data = await api.get_client_type() + assert data is not None + assert isinstance(data, TradeClientType) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_trade(api): + data = await api.get_trade() + assert data is not None + assert isinstance(data, list) + assert len(data) > 0 + assert isinstance(data[0], Trade) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_closing_price_info(api): + data = await api.get_closing_price_info() + assert data is not None + assert isinstance(data, ClosingPriceData) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_best_limit(api): + data = await api.get_best_limits() + assert data is not None + assert isinstance(data, BestLimit) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_best_limit_history(api): + date = "20231015" + data = await api.get_best_limit_history(date=date) + assert data is not None + assert isinstance(data, list) + assert len(data) > 0 + assert isinstance(data[0], BestLimitHistory) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_share_holder(api): + date = "20231015" + data = await api.get_share_holder(date=date) + assert data is not None + assert isinstance(data, list) + assert len(data) > 0 + assert isinstance(data[0], ShareHolderItem) diff --git a/tests/test_download_index.py b/tests/test_download_index.py new file mode 100644 index 0000000..022c421 --- /dev/null +++ b/tests/test_download_index.py @@ -0,0 +1,27 @@ +import pytest +from tehran_stocks.download.index import IndexDetailsAPI, IndexType +from tehran_stocks.schema.index import IndexHistoryItem + + +@pytest.fixture(scope="module") +def api(): + return IndexDetailsAPI(IndexType.TOTAL_INDEX.value) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_index_history(api: IndexDetailsAPI): + data = await api.get_index_history() + assert len(data) > 1000 + assert isinstance(data[0], IndexHistoryItem) + + +# get_index_companies +# get_index_info + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_index_companies(api: IndexDetailsAPI): + data = await api.get_index_companies() + assert len(data) > 1 diff --git a/tests/test_download_name.py b/tests/test_download_name.py index 80663a5..2a27961 100644 --- a/tests/test_download_name.py +++ b/tests/test_download_name.py @@ -1,44 +1,26 @@ import pytest from tehran_stocks import download +from tehran_stocks.download.names import InstrumentList -SAIPA = "44891482026867833" +SAIPA = 44891482026867833 -def test_id_from_group(): - ids = download.get_stock_ids() - assert ids, "no id available" - assert SAIPA in ids, "Saipa is not in group" +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_ins_codes(): + res = await InstrumentList().get_ins_codes() + assert res, "no data available" + assert (SAIPA, "IRO1SIPA0001") in res, "Saipa is not in group" + assert len(res) > 300, "there is a problem during downloading stocks" +@pytest.mark.online def test_get_groups(): groups = download.get_stock_groups() size = len(groups) assert size > 60, "there is a problem during downloading groups" -def test_get_detail(): - data = download.get_stock_detail(SAIPA) - assert data["code"] == SAIPA - keys = [ - "code", - "instId", - "insCode", - "baseVol", - "name", - "group_name", - "title", - "sectorPe", - "shareCount", - "estimatedEps", - "group_code", - ] - for key in keys: - assert key in data.keys() - data = download.get_stock_detail("123") - if isinstance(data, bool): - assert data == False - - # def test_error_get_detail(self): # FAKE = 278496000000000 # be_false = download.get_stock_detail(FAKE, 23) diff --git a/tests/test_download_price.py b/tests/test_download_price.py index e1d10e7..59a38f5 100644 --- a/tests/test_download_price.py +++ b/tests/test_download_price.py @@ -1,29 +1,48 @@ import pytest -from tehran_stocks import download -from tehran_stocks.download.price import get_stock_price_history - -SAIPA = "44891482026867833" - - -def test_get_stock_price_history(): - data = get_stock_price_history(SAIPA) - columns = ['ticker', - 'dtyyyymmdd', - 'first', - 'high', - 'low', - 'close', - 'value', - 'vol', - 'openint', - 'per', - 'open'] - assert data.columns.tolist() == columns +from tehran_stocks.download.price import InstrumentPriceHistory +from tehran_stocks.schema.price import PriceAdjustItem +SAIPA = 44891482026867833 + + +@pytest.fixture(scope="module") +def api(): + return InstrumentPriceHistory(SAIPA) + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_stock_price_history(api: InstrumentPriceHistory): + data = await api.get_stock_price_history() + columns = { + "ticker", + "dtyyyymmdd", + "first", + "high", + "low", + "close", + "value", + "vol", + "openint", + "per", + "open", + } + assert set(columns).issubset(set(data.columns)) + assert "date_shamsi" in data.columns + assert len(data) > 1000 + + +@pytest.mark.online +@pytest.mark.asyncio +async def test_get_price_with_date(api): + data = await api.get_stock_price_history(start_date="20200104", end_date="20200106") + assert len(data) == 2 + + +@pytest.mark.online @pytest.mark.asyncio -async def test_update_stock_price(): - status, code = await download.update_stock_price(SAIPA) - assert status == True - assert code == SAIPA - status, code = await download.update_stock_price(f'{SAIPA}121') - assert status != True +async def test_get_price_adjusted(api: InstrumentPriceHistory): + data = await api.get_price_adjusted() + assert len(data) > 0 + assert isinstance(data[0], PriceAdjustItem) + assert data[0].ins_code == SAIPA