├── .coveragerc ├── .env-exemple ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Pipfile ├── Pipfile.lock ├── README.md ├── config ├── __init__.py └── redis │ ├── __init__.py │ ├── conect_redis.py │ ├── get_env_redis.py │ └── randler_redis.py ├── docker-compose.yml ├── dockerfile ├── main.py ├── pytest.ini ├── requirements.tx ├── requirements.txt ├── src ├── Exceptions │ ├── __init__.py │ ├── artist_not_found_exception.py │ ├── find_string_in_lyric_exception.py │ ├── get_found_sentence_to_json_exception.py │ ├── get_links_musics_exception.py │ └── set_sentence_exception.py ├── __init__.py ├── controllers │ └── words_in_songs_controller.py ├── models │ ├── __init__.py │ ├── artist_sentence_model.py │ └── setence_found_model.py ├── repository │ └── words_in_songs.py └── views │ └── word_in_songs_view.py └── tests ├── __init__.py ├── http └── api_test.http ├── test_class_wis.py ├── test_fast_api.py └── test_redis.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [html] 2 | directory = ./tests/report -------------------------------------------------------------------------------- /.env-exemple: -------------------------------------------------------------------------------- 1 | REDIS_HOST=localhost 2 | REDIS_PORT=6379 3 | REDIS_DB=0 -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: [ "3.8" ] 12 | 13 | services: 14 | redis: 15 | image: redis 16 | env: 17 | REDIS_HOST: ${{ vars.REDIS_HOST }} 18 | REDIS_PORT: ${{ vars.REDIS_PORT }} 19 | REDIS_DB: ${{ vars.REDIS_DB }} 20 | ports: 21 | - 6379:6379 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install dependencies 32 | run: | 33 | pip install pipenv 34 | pipenv install --system 35 | 36 | - name: Test with pytest 37 | run: | 38 | pytest 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .idea 6 | credenciais.py 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | !/tests/coverage_html_report/ -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | fastapi = "*" 8 | loguru = "*" 9 | requests = "*" 10 | beautifulsoup4 = "*" 11 | python-dotenv = "*" 12 | pytest = "*" 13 | redis = "*" 14 | uvicorn = "*" 15 | pytest-cov = "*" 16 | httpx = "*" 17 | 18 | [dev-packages] 19 | 20 | [requires] 21 | python_version = "3.8" 22 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "5364fe91e8e9dd3e0f543f7217b8435cc2fe1f2cbd3ddf98ceba65c3621582ef" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "anyio": { 20 | "hashes": [ 21 | "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421", 22 | "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3" 23 | ], 24 | "markers": "python_full_version >= '3.6.2'", 25 | "version": "==3.6.2" 26 | }, 27 | "async-timeout": { 28 | "hashes": [ 29 | "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", 30 | "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" 31 | ], 32 | "markers": "python_version >= '3.6'", 33 | "version": "==4.0.2" 34 | }, 35 | "attrs": { 36 | "hashes": [ 37 | "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", 38 | "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" 39 | ], 40 | "markers": "python_version >= '3.6'", 41 | "version": "==22.2.0" 42 | }, 43 | "beautifulsoup4": { 44 | "hashes": [ 45 | "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39", 46 | "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106" 47 | ], 48 | "index": "pypi", 49 | "version": "==4.11.2" 50 | }, 51 | "certifi": { 52 | "hashes": [ 53 | "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", 54 | "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" 55 | ], 56 | "markers": "python_version >= '3.6'", 57 | "version": "==2022.12.7" 58 | }, 59 | "charset-normalizer": { 60 | "hashes": [ 61 | "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", 62 | "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", 63 | "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", 64 | "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", 65 | "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", 66 | "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", 67 | "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", 68 | "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", 69 | "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", 70 | "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", 71 | "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", 72 | "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", 73 | "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", 74 | "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", 75 | "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", 76 | "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", 77 | "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", 78 | "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", 79 | "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", 80 | "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", 81 | "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", 82 | "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", 83 | "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", 84 | "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", 85 | "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", 86 | "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", 87 | "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", 88 | "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", 89 | "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", 90 | "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", 91 | "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", 92 | "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", 93 | "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", 94 | "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", 95 | "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", 96 | "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", 97 | "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", 98 | "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", 99 | "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", 100 | "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", 101 | "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", 102 | "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", 103 | "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", 104 | "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", 105 | "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", 106 | "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", 107 | "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", 108 | "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", 109 | "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", 110 | "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", 111 | "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", 112 | "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", 113 | "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", 114 | "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", 115 | "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", 116 | "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", 117 | "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", 118 | "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", 119 | "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", 120 | "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", 121 | "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", 122 | "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", 123 | "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", 124 | "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", 125 | "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", 126 | "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", 127 | "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", 128 | "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", 129 | "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", 130 | "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", 131 | "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", 132 | "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", 133 | "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", 134 | "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", 135 | "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" 136 | ], 137 | "markers": "python_full_version >= '3.7.0'", 138 | "version": "==3.1.0" 139 | }, 140 | "click": { 141 | "hashes": [ 142 | "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", 143 | "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" 144 | ], 145 | "markers": "python_version >= '3.7'", 146 | "version": "==8.1.3" 147 | }, 148 | "colorama": { 149 | "hashes": [ 150 | "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", 151 | "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" 152 | ], 153 | "markers": "sys_platform == 'win32'", 154 | "version": "==0.4.6" 155 | }, 156 | "coverage": { 157 | "extras": [ 158 | "toml" 159 | ], 160 | "hashes": [ 161 | "sha256:006ed5582e9cbc8115d2e22d6d2144a0725db542f654d9d4fda86793832f873d", 162 | "sha256:046936ab032a2810dcaafd39cc4ef6dd295df1a7cbead08fe996d4765fca9fe4", 163 | "sha256:0484d9dd1e6f481b24070c87561c8d7151bdd8b044c93ac99faafd01f695c78e", 164 | "sha256:0ce383d5f56d0729d2dd40e53fe3afeb8f2237244b0975e1427bfb2cf0d32bab", 165 | "sha256:186e0fc9cf497365036d51d4d2ab76113fb74f729bd25da0975daab2e107fd90", 166 | "sha256:2199988e0bc8325d941b209f4fd1c6fa007024b1442c5576f1a32ca2e48941e6", 167 | "sha256:299bc75cb2a41e6741b5e470b8c9fb78d931edbd0cd009c58e5c84de57c06731", 168 | "sha256:3668291b50b69a0c1ef9f462c7df2c235da3c4073f49543b01e7eb1dee7dd540", 169 | "sha256:36dd42da34fe94ed98c39887b86db9d06777b1c8f860520e21126a75507024f2", 170 | "sha256:38004671848b5745bb05d4d621526fca30cee164db42a1f185615f39dc997292", 171 | "sha256:387fb46cb8e53ba7304d80aadca5dca84a2fbf6fe3faf6951d8cf2d46485d1e5", 172 | "sha256:3eb55b7b26389dd4f8ae911ba9bc8c027411163839dea4c8b8be54c4ee9ae10b", 173 | "sha256:420f94a35e3e00a2b43ad5740f935358e24478354ce41c99407cddd283be00d2", 174 | "sha256:4ac0f522c3b6109c4b764ffec71bf04ebc0523e926ca7cbe6c5ac88f84faced0", 175 | "sha256:4c752d5264053a7cf2fe81c9e14f8a4fb261370a7bb344c2a011836a96fb3f57", 176 | "sha256:4f01911c010122f49a3e9bdc730eccc66f9b72bd410a3a9d3cb8448bb50d65d3", 177 | "sha256:4f68ee32d7c4164f1e2c8797535a6d0a3733355f5861e0f667e37df2d4b07140", 178 | "sha256:4fa54fb483decc45f94011898727802309a109d89446a3c76387d016057d2c84", 179 | "sha256:507e4720791977934bba016101579b8c500fb21c5fa3cd4cf256477331ddd988", 180 | "sha256:53d0fd4c17175aded9c633e319360d41a1f3c6e352ba94edcb0fa5167e2bad67", 181 | "sha256:55272f33da9a5d7cccd3774aeca7a01e500a614eaea2a77091e9be000ecd401d", 182 | "sha256:5764e1f7471cb8f64b8cda0554f3d4c4085ae4b417bfeab236799863703e5de2", 183 | "sha256:57b77b9099f172804e695a40ebaa374f79e4fb8b92f3e167f66facbf92e8e7f5", 184 | "sha256:5afdad4cc4cc199fdf3e18088812edcf8f4c5a3c8e6cb69127513ad4cb7471a9", 185 | "sha256:5cc0783844c84af2522e3a99b9b761a979a3ef10fb87fc4048d1ee174e18a7d8", 186 | "sha256:5e1df45c23d4230e3d56d04414f9057eba501f78db60d4eeecfcb940501b08fd", 187 | "sha256:6146910231ece63facfc5984234ad1b06a36cecc9fd0c028e59ac7c9b18c38c6", 188 | "sha256:797aad79e7b6182cb49c08cc5d2f7aa7b2128133b0926060d0a8889ac43843be", 189 | "sha256:7c20b731211261dc9739bbe080c579a1835b0c2d9b274e5fcd903c3a7821cf88", 190 | "sha256:817295f06eacdc8623dc4df7d8b49cea65925030d4e1e2a7c7218380c0072c25", 191 | "sha256:81f63e0fb74effd5be736cfe07d710307cc0a3ccb8f4741f7f053c057615a137", 192 | "sha256:872d6ce1f5be73f05bea4df498c140b9e7ee5418bfa2cc8204e7f9b817caa968", 193 | "sha256:8c99cb7c26a3039a8a4ee3ca1efdde471e61b4837108847fb7d5be7789ed8fd9", 194 | "sha256:8dbe2647bf58d2c5a6c5bcc685f23b5f371909a5624e9f5cd51436d6a9f6c6ef", 195 | "sha256:8efb48fa743d1c1a65ee8787b5b552681610f06c40a40b7ef94a5b517d885c54", 196 | "sha256:92ebc1619650409da324d001b3a36f14f63644c7f0a588e331f3b0f67491f512", 197 | "sha256:9d22e94e6dc86de981b1b684b342bec5e331401599ce652900ec59db52940005", 198 | "sha256:ba279aae162b20444881fc3ed4e4f934c1cf8620f3dab3b531480cf602c76b7f", 199 | "sha256:bc4803779f0e4b06a2361f666e76f5c2e3715e8e379889d02251ec911befd149", 200 | "sha256:bfe7085783cda55e53510482fa7b5efc761fad1abe4d653b32710eb548ebdd2d", 201 | "sha256:c448b5c9e3df5448a362208b8d4b9ed85305528313fca1b479f14f9fe0d873b8", 202 | "sha256:c90e73bdecb7b0d1cea65a08cb41e9d672ac6d7995603d6465ed4914b98b9ad7", 203 | "sha256:d2b96123a453a2d7f3995ddb9f28d01fd112319a7a4d5ca99796a7ff43f02af5", 204 | "sha256:d52f0a114b6a58305b11a5cdecd42b2e7f1ec77eb20e2b33969d702feafdd016", 205 | "sha256:d530191aa9c66ab4f190be8ac8cc7cfd8f4f3217da379606f3dd4e3d83feba69", 206 | "sha256:d683d230b5774816e7d784d7ed8444f2a40e7a450e5720d58af593cb0b94a212", 207 | "sha256:db45eec1dfccdadb179b0f9ca616872c6f700d23945ecc8f21bb105d74b1c5fc", 208 | "sha256:db8c2c5ace167fd25ab5dd732714c51d4633f58bac21fb0ff63b0349f62755a8", 209 | "sha256:e2926b8abedf750c2ecf5035c07515770944acf02e1c46ab08f6348d24c5f94d", 210 | "sha256:e627dee428a176ffb13697a2c4318d3f60b2ccdde3acdc9b3f304206ec130ccd", 211 | "sha256:efe1c0adad110bf0ad7fb59f833880e489a61e39d699d37249bdf42f80590169" 212 | ], 213 | "markers": "python_version >= '3.7'", 214 | "version": "==7.2.2" 215 | }, 216 | "exceptiongroup": { 217 | "hashes": [ 218 | "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e", 219 | "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785" 220 | ], 221 | "markers": "python_version < '3.11'", 222 | "version": "==1.1.1" 223 | }, 224 | "fastapi": { 225 | "hashes": [ 226 | "sha256:451387550c2d25a972193f22e408a82e75a8e7867c834a03076704fe20df3256", 227 | "sha256:4a75936dbf9eb74be5eb0d41a793adefe9f3fc6ba66dbdabd160120fd3c2d9cd" 228 | ], 229 | "index": "pypi", 230 | "version": "==0.94.1" 231 | }, 232 | "h11": { 233 | "hashes": [ 234 | "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", 235 | "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" 236 | ], 237 | "markers": "python_version >= '3.7'", 238 | "version": "==0.14.0" 239 | }, 240 | "httpcore": { 241 | "hashes": [ 242 | "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb", 243 | "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0" 244 | ], 245 | "markers": "python_version >= '3.7'", 246 | "version": "==0.16.3" 247 | }, 248 | "httpx": { 249 | "hashes": [ 250 | "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9", 251 | "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6" 252 | ], 253 | "index": "pypi", 254 | "version": "==0.23.3" 255 | }, 256 | "idna": { 257 | "hashes": [ 258 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 259 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 260 | ], 261 | "markers": "python_version >= '3.5'", 262 | "version": "==3.4" 263 | }, 264 | "iniconfig": { 265 | "hashes": [ 266 | "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", 267 | "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" 268 | ], 269 | "markers": "python_version >= '3.7'", 270 | "version": "==2.0.0" 271 | }, 272 | "loguru": { 273 | "hashes": [ 274 | "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c", 275 | "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3" 276 | ], 277 | "index": "pypi", 278 | "version": "==0.6.0" 279 | }, 280 | "packaging": { 281 | "hashes": [ 282 | "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", 283 | "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" 284 | ], 285 | "markers": "python_version >= '3.7'", 286 | "version": "==23.0" 287 | }, 288 | "pluggy": { 289 | "hashes": [ 290 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 291 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 292 | ], 293 | "markers": "python_version >= '3.6'", 294 | "version": "==1.0.0" 295 | }, 296 | "pydantic": { 297 | "hashes": [ 298 | "sha256:012c99a9c0d18cfde7469aa1ebff922e24b0c706d03ead96940f5465f2c9cf62", 299 | "sha256:0abd9c60eee6201b853b6c4be104edfba4f8f6c5f3623f8e1dba90634d63eb35", 300 | "sha256:12e837fd320dd30bd625be1b101e3b62edc096a49835392dcf418f1a5ac2b832", 301 | "sha256:163e79386c3547c49366e959d01e37fc30252285a70619ffc1b10ede4758250a", 302 | "sha256:189318051c3d57821f7233ecc94708767dd67687a614a4e8f92b4a020d4ffd06", 303 | "sha256:1c84583b9df62522829cbc46e2b22e0ec11445625b5acd70c5681ce09c9b11c4", 304 | "sha256:3091d2eaeda25391405e36c2fc2ed102b48bac4b384d42b2267310abae350ca6", 305 | "sha256:32937835e525d92c98a1512218db4eed9ddc8f4ee2a78382d77f54341972c0e7", 306 | "sha256:3a2be0a0f32c83265fd71a45027201e1278beaa82ea88ea5b345eea6afa9ac7f", 307 | "sha256:3ac1cd4deed871dfe0c5f63721e29debf03e2deefa41b3ed5eb5f5df287c7b70", 308 | "sha256:3ce13a558b484c9ae48a6a7c184b1ba0e5588c5525482681db418268e5f86186", 309 | "sha256:415a3f719ce518e95a92effc7ee30118a25c3d032455d13e121e3840985f2efd", 310 | "sha256:43cdeca8d30de9a897440e3fb8866f827c4c31f6c73838e3a01a14b03b067b1d", 311 | "sha256:476f6674303ae7965730a382a8e8d7fae18b8004b7b69a56c3d8fa93968aa21c", 312 | "sha256:4c19eb5163167489cb1e0161ae9220dadd4fc609a42649e7e84a8fa8fff7a80f", 313 | "sha256:4ca83739c1263a044ec8b79df4eefc34bbac87191f0a513d00dd47d46e307a65", 314 | "sha256:528dcf7ec49fb5a84bf6fe346c1cc3c55b0e7603c2123881996ca3ad79db5bfc", 315 | "sha256:53de12b4608290992a943801d7756f18a37b7aee284b9ffa794ee8ea8153f8e2", 316 | "sha256:587d92831d0115874d766b1f5fddcdde0c5b6c60f8c6111a394078ec227fca6d", 317 | "sha256:60184e80aac3b56933c71c48d6181e630b0fbc61ae455a63322a66a23c14731a", 318 | "sha256:6195ca908045054dd2d57eb9c39a5fe86409968b8040de8c2240186da0769da7", 319 | "sha256:61f1f08adfaa9cc02e0cbc94f478140385cbd52d5b3c5a657c2fceb15de8d1fb", 320 | "sha256:72cb30894a34d3a7ab6d959b45a70abac8a2a93b6480fc5a7bfbd9c935bdc4fb", 321 | "sha256:751f008cd2afe812a781fd6aa2fb66c620ca2e1a13b6a2152b1ad51553cb4b77", 322 | "sha256:89f15277d720aa57e173954d237628a8d304896364b9de745dcb722f584812c7", 323 | "sha256:8c32b6bba301490d9bb2bf5f631907803135e8085b6aa3e5fe5a770d46dd0160", 324 | "sha256:acc6783751ac9c9bc4680379edd6d286468a1dc8d7d9906cd6f1186ed682b2b0", 325 | "sha256:b1eb6610330a1dfba9ce142ada792f26bbef1255b75f538196a39e9e90388bf4", 326 | "sha256:b243b564cea2576725e77aeeda54e3e0229a168bc587d536cd69941e6797543d", 327 | "sha256:b41822064585fea56d0116aa431fbd5137ce69dfe837b599e310034171996084", 328 | "sha256:bbd5c531b22928e63d0cb1868dee76123456e1de2f1cb45879e9e7a3f3f1779b", 329 | "sha256:cf95adb0d1671fc38d8c43dd921ad5814a735e7d9b4d9e437c088002863854fd", 330 | "sha256:e277bd18339177daa62a294256869bbe84df1fb592be2716ec62627bb8d7c81d", 331 | "sha256:ea4e2a7cb409951988e79a469f609bba998a576e6d7b9791ae5d1e0619e1c0f2", 332 | "sha256:f9289065611c48147c1dd1fd344e9d57ab45f1d99b0fb26c51f1cf72cd9bcd31", 333 | "sha256:fd9b9e98068fa1068edfc9eabde70a7132017bdd4f362f8b4fd0abed79c33083" 334 | ], 335 | "markers": "python_version >= '3.7'", 336 | "version": "==1.10.6" 337 | }, 338 | "pytest": { 339 | "hashes": [ 340 | "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e", 341 | "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4" 342 | ], 343 | "index": "pypi", 344 | "version": "==7.2.2" 345 | }, 346 | "pytest-cov": { 347 | "hashes": [ 348 | "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b", 349 | "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470" 350 | ], 351 | "index": "pypi", 352 | "version": "==4.0.0" 353 | }, 354 | "python-dotenv": { 355 | "hashes": [ 356 | "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", 357 | "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" 358 | ], 359 | "index": "pypi", 360 | "version": "==1.0.0" 361 | }, 362 | "redis": { 363 | "hashes": [ 364 | "sha256:1eec3741cda408d3a5f84b78d089c8b8d895f21b3b050988351e925faf202864", 365 | "sha256:5deb072d26e67d2be1712603bfb7947ec3431fb0eec9c578994052e33035af6d" 366 | ], 367 | "index": "pypi", 368 | "version": "==4.5.1" 369 | }, 370 | "requests": { 371 | "hashes": [ 372 | "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", 373 | "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" 374 | ], 375 | "index": "pypi", 376 | "version": "==2.28.2" 377 | }, 378 | "rfc3986": { 379 | "extras": [ 380 | "idna2008" 381 | ], 382 | "hashes": [ 383 | "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", 384 | "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" 385 | ], 386 | "version": "==1.5.0" 387 | }, 388 | "sniffio": { 389 | "hashes": [ 390 | "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", 391 | "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" 392 | ], 393 | "markers": "python_version >= '3.7'", 394 | "version": "==1.3.0" 395 | }, 396 | "soupsieve": { 397 | "hashes": [ 398 | "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955", 399 | "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a" 400 | ], 401 | "markers": "python_version >= '3.7'", 402 | "version": "==2.4" 403 | }, 404 | "starlette": { 405 | "hashes": [ 406 | "sha256:41da799057ea8620e4667a3e69a5b1923ebd32b1819c8fa75634bbe8d8bea9bd", 407 | "sha256:e87fce5d7cbdde34b76f0ac69013fd9d190d581d80681493016666e6f96c6d5e" 408 | ], 409 | "markers": "python_version >= '3.7'", 410 | "version": "==0.26.1" 411 | }, 412 | "tomli": { 413 | "hashes": [ 414 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 415 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 416 | ], 417 | "markers": "python_version < '3.11'", 418 | "version": "==2.0.1" 419 | }, 420 | "typing-extensions": { 421 | "hashes": [ 422 | "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", 423 | "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4" 424 | ], 425 | "markers": "python_version >= '3.7'", 426 | "version": "==4.5.0" 427 | }, 428 | "urllib3": { 429 | "hashes": [ 430 | "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", 431 | "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" 432 | ], 433 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 434 | "version": "==1.26.15" 435 | }, 436 | "uvicorn": { 437 | "hashes": [ 438 | "sha256:0fac9cb342ba099e0d582966005f3fdba5b0290579fed4a6266dc702ca7bb032", 439 | "sha256:e47cac98a6da10cd41e6fd036d472c6f58ede6c5dbee3dbee3ef7a100ed97742" 440 | ], 441 | "index": "pypi", 442 | "version": "==0.21.1" 443 | }, 444 | "win32-setctime": { 445 | "hashes": [ 446 | "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2", 447 | "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad" 448 | ], 449 | "markers": "sys_platform == 'win32'", 450 | "version": "==1.1.0" 451 | } 452 | }, 453 | "develop": {} 454 | } 455 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Words-In-Songs 2 | 3 | ![Star](https://img.shields.io/github/stars/Erickson-lopes-dev/Words-In-Songs?style=social) [![LinkedIn](https://img.shields.io/badge/LinkedIn-Erickson_Lopes%20-blue)](https://www.linkedin.com/in/ericksonlopes/) 4 | [![wakatime](https://wakatime.com/badge/user/541772df-f19f-4145-a40c-cf7ffac73ea5/project/0a7f661a-d99d-450d-9180-049bc3418b55.svg)](https://wakatime.com/badge/user/541772df-f19f-4145-a40c-cf7ffac73ea5/project/0a7f661a-d99d-450d-9180-049bc3418b55) 5 | [![Tests](https://github.com/Erickson-lopes-dev/Words-In-Songs/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/Erickson-lopes-dev/Words-In-Songs/actions/workflows/tests.yml) 6 | 7 | ## Technologies 8 | ![FastAPI](https://img.shields.io/badge/-FastAPI-009688?&logo=FastAPI&logoColor=FFFFFF) ![Docker](https://img.shields.io/badge/-Docker-2496ED?&logo=Docker&logoColor=FFFFFF) ![Pytest](https://img.shields.io/badge/-Pytest-0A9EDC?&logo=Pytest&logoColor=FFFFFF) ![Redis](https://img.shields.io/badge/-Redis-DC382D?&logo=Redis&logoColor=FFFFFF) ![Gunicorn](https://img.shields.io/badge/-Gunicorn-499848?&logo=gunicorn&logoColor=FFFFFF) 9 | 10 | "E se eu criar uma ferramente onde eu possa pesquisar por todas as músicas de um artista uma determinada frase ou palavra" 11 | 12 | Words-In-Songs é um projeto inovador que visa criar uma ferramenta de pesquisa que permita aos usuários buscar por palavras ou frases específicas em todas as músicas de um artista selecionado. Com essa funcionalidade única, os usuários poderão explorar e descobrir como determinadas palavras são utilizadas nas letras das músicas de seus artistas favoritos. O projeto busca proporcionar uma experiência eficiente e abrangente para aqueles que desejam compreender melhor a temática e o estilo lírico de seus artistas preferidos. 13 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | # Redis 2 | 3 | from config.redis import RandlerRedis, ConnectRedis, RedisEnvConfig 4 | 5 | PROJECT_NAME: str = "Words In Songs" 6 | -------------------------------------------------------------------------------- /config/redis/__init__.py: -------------------------------------------------------------------------------- 1 | from config.redis.conect_redis import ConnectRedis 2 | from config.redis.get_env_redis import RedisEnvConfig 3 | from config.redis.randler_redis import RandlerRedis 4 | -------------------------------------------------------------------------------- /config/redis/conect_redis.py: -------------------------------------------------------------------------------- 1 | import redis 2 | 3 | from config.redis.get_env_redis import RedisEnvConfig 4 | 5 | 6 | class ConnectRedis: 7 | def __init__(self) -> None: 8 | self.__redis_settings = RedisEnvConfig() 9 | 10 | redis_pool = redis.ConnectionPool( 11 | host=self.__redis_settings.HOST, 12 | port=self.__redis_settings.PORT, 13 | db=self.__redis_settings.DB, 14 | ) 15 | redis_client = redis.Redis(connection_pool=redis_pool) 16 | 17 | self.__redis = redis_client 18 | 19 | def __enter__(self): 20 | return self.__redis 21 | 22 | def __exit__(self, exc_type, exc_val, exc_tb) -> None: 23 | self.__redis.close() 24 | -------------------------------------------------------------------------------- /config/redis/get_env_redis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pydantic import BaseSettings 4 | 5 | 6 | class RedisEnvConfig(BaseSettings): 7 | PORT = os.getenv('REDIS_PORT') or 6379 8 | HOST = os.getenv('REDIS_HOST') or 'localhost' 9 | DB = os.getenv('REDIS_DB') or 0 10 | -------------------------------------------------------------------------------- /config/redis/randler_redis.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List 3 | 4 | from config.redis.conect_redis import ConnectRedis 5 | from src.Exceptions import GetSentenceToJsonException, SetSentenceException 6 | from src.models import SetenceFound 7 | 8 | 9 | class RandlerRedis: 10 | @staticmethod 11 | def get_found_sentence_to_json(key: str) -> dict or bool: 12 | try: 13 | with ConnectRedis() as r: 14 | if r.exists(key): 15 | response = r.hget(key, "found") 16 | response = response.decode("utf-8").replace("'", '"') 17 | 18 | dicio = json.loads(response) 19 | return dicio 20 | 21 | except Exception: 22 | raise GetSentenceToJsonException 23 | 24 | return False 25 | 26 | def set_sentence_found(self, key, values: List[SetenceFound]): 27 | try: 28 | str_list_sentence = str(list(map(lambda sentence_found: sentence_found.__dict__, values))) 29 | 30 | with ConnectRedis() as self.__redis: 31 | self.__redis.hset(key, "found", str_list_sentence) 32 | self.__redis.expire(key, 1000) 33 | except Exception: 34 | raise SetSentenceException 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | redis: 5 | image: redis 6 | ports: 7 | - "6379:6379" 8 | volumes: 9 | - redis_data:/data 10 | 11 | app: 12 | build: . 13 | ports: 14 | - "8000:8000" 15 | depends_on: 16 | - redis 17 | 18 | volumes: 19 | redis_data: 20 | 21 | networks: 22 | words-in-songs-network: 23 | driver: bridge 24 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | WORKDIR /words-in-songs 4 | 5 | COPY . . 6 | 7 | RUN pip install pipenv 8 | RUN pipenv install --system 9 | 10 | ENV REDIS_HOST=redis 11 | ENV REDIS_PORT=6379 12 | ENV REDIS_DB=0 13 | 14 | EXPOSE 8000 15 | 16 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] 17 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from config import PROJECT_NAME 4 | from src.views.word_in_songs_view import wis_router 5 | 6 | app = FastAPI(title=PROJECT_NAME) 7 | 8 | app.include_router(wis_router, prefix="/api/v1", tags=["wis"]) 9 | 10 | if __name__ == '__main__': 11 | """ Executar localmente """ 12 | import uvicorn 13 | from dotenv import load_dotenv 14 | 15 | load_dotenv() 16 | uvicorn.run(app, host="localhost", port=8001) 17 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v --cov=src --cov-report=term-missing --cov-report=html 3 | 4 | -------------------------------------------------------------------------------- /requirements.tx: -------------------------------------------------------------------------------- 1 | -i https://pypi.org/simple 2 | anyio==3.6.2 ; python_full_version >= '3.6.2' 3 | async-timeout==4.0.2 ; python_version >= '3.6' 4 | attrs==22.2.0 ; python_version >= '3.6' 5 | beautifulsoup4==4.11.2 6 | certifi==2022.12.7 ; python_version >= '3.6' 7 | charset-normalizer==3.1.0 ; python_full_version >= '3.7.0' 8 | click==8.1.3 ; python_version >= '3.7' 9 | colorama==0.4.6 ; sys_platform == 'win32' 10 | coverage[toml]==7.2.2 ; python_version >= '3.7' 11 | exceptiongroup==1.1.1 ; python_version < '3.11' 12 | fastapi==0.94.1 13 | h11==0.14.0 ; python_version >= '3.7' 14 | httpcore==0.16.3 ; python_version >= '3.7' 15 | httpx==0.23.3 16 | idna==3.4 ; python_version >= '3.5' 17 | iniconfig==2.0.0 ; python_version >= '3.7' 18 | loguru==0.6.0 19 | packaging==23.0 ; python_version >= '3.7' 20 | pluggy==1.0.0 ; python_version >= '3.6' 21 | pydantic==1.10.6 ; python_version >= '3.7' 22 | pytest==7.2.2 23 | pytest-cov==4.0.0 24 | python-dotenv==1.0.0 25 | redis==4.5.1 26 | requests==2.28.2 27 | rfc3986[idna2008]==1.5.0 28 | sniffio==1.3.0 ; python_version >= '3.7' 29 | soupsieve==2.4 ; python_version >= '3.7' 30 | starlette==0.26.1 ; python_version >= '3.7' 31 | tomli==2.0.1 ; python_version < '3.11' 32 | typing-extensions==4.5.0 ; python_version >= '3.7' 33 | urllib3==1.26.15 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' 34 | uvicorn==0.21.1 35 | win32-setctime==1.1.0 ; sys_platform == 'win32' 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -i https://pypi.org/simple 2 | anyio==3.6.2 ; python_full_version >= '3.6.2' 3 | async-timeout==4.0.2 ; python_version >= '3.6' 4 | attrs==22.2.0 ; python_version >= '3.6' 5 | beautifulsoup4==4.11.2 6 | certifi==2022.12.7 ; python_version >= '3.6' 7 | charset-normalizer==3.1.0 ; python_full_version >= '3.7.0' 8 | click==8.1.3 ; python_version >= '3.7' 9 | colorama==0.4.6 ; sys_platform == 'win32' 10 | exceptiongroup==1.1.1 ; python_version < '3.11' 11 | fastapi==0.94.1 12 | h11==0.14.0 ; python_version >= '3.7' 13 | idna==3.4 ; python_version >= '3.5' 14 | iniconfig==2.0.0 ; python_version >= '3.7' 15 | loguru==0.6.0 16 | packaging==23.0 ; python_version >= '3.7' 17 | pluggy==1.0.0 ; python_version >= '3.6' 18 | pydantic==1.10.6 ; python_version >= '3.7' 19 | pytest==7.2.2 20 | python-dotenv==1.0.0 21 | redis==4.5.1 22 | requests==2.28.2 23 | sniffio==1.3.0 ; python_version >= '3.7' 24 | soupsieve==2.4 ; python_version >= '3.7' 25 | starlette==0.26.1 ; python_version >= '3.7' 26 | tomli==2.0.1 ; python_version < '3.11' 27 | typing-extensions==4.5.0 ; python_version >= '3.7' 28 | urllib3==1.26.15 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' 29 | uvicorn==0.21.1 30 | win32-setctime==1.1.0 ; sys_platform == 'win32' 31 | -------------------------------------------------------------------------------- /src/Exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from src.Exceptions.artist_not_found_exception import ArtistNotFoundException 2 | from src.Exceptions.find_string_in_lyric_exception import FindStringInLyricException 3 | from src.Exceptions.get_found_sentence_to_json_exception import GetSentenceToJsonException 4 | from src.Exceptions.get_links_musics_exception import GetLinksMusicsException 5 | from src.Exceptions.set_sentence_exception import SetSentenceException 6 | -------------------------------------------------------------------------------- /src/Exceptions/artist_not_found_exception.py: -------------------------------------------------------------------------------- 1 | class ArtistNotFoundException(Exception): 2 | """ Artista não encontrado """ 3 | 4 | def __init__(self, artist: str): 5 | self.artist = artist 6 | 7 | def __str__(self): 8 | return f"Artista '{self.artist}' não encontrado" 9 | -------------------------------------------------------------------------------- /src/Exceptions/find_string_in_lyric_exception.py: -------------------------------------------------------------------------------- 1 | class FindStringInLyricException(Exception): 2 | """ Erro ao encontrar a string na letra da música """ 3 | 4 | def __init__(self, link: str): 5 | self.link = link 6 | 7 | def __str__(self): 8 | return f"Erro ao encontrar a string na letra da música '{self.link}'" 9 | -------------------------------------------------------------------------------- /src/Exceptions/get_found_sentence_to_json_exception.py: -------------------------------------------------------------------------------- 1 | class GetSentenceToJsonException(Exception): 2 | """ Erro ao converter a lista do banco para json """ 3 | 4 | def __str__(self): 5 | return "Erro ao converter a lista do banco para json" 6 | -------------------------------------------------------------------------------- /src/Exceptions/get_links_musics_exception.py: -------------------------------------------------------------------------------- 1 | class GetLinksMusicsException(Exception): 2 | """ Erro ao capturar os links das músicas do artista """ 3 | 4 | def __init__(self, artist: str): 5 | self.artist = artist 6 | 7 | def __str__(self): 8 | return f"Erro ao capturar os links das músicas do artista '{self.artist}'" 9 | -------------------------------------------------------------------------------- /src/Exceptions/set_sentence_exception.py: -------------------------------------------------------------------------------- 1 | class SetSentenceException(Exception): 2 | """ Retorna uma exceção quando não consegue salvar os objetos no redis """ 3 | 4 | def __str__(self): 5 | return "Erro ao salvar os objetos no redis" 6 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericksonlopes/Words-In-Songs/68b776246f7a8c6950427a48ea05b186dc1e2460/src/__init__.py -------------------------------------------------------------------------------- /src/controllers/words_in_songs_controller.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import concurrent.futures 3 | from typing import List 4 | 5 | from fastapi import HTTPException 6 | from loguru import logger 7 | 8 | from config import RandlerRedis 9 | from src.Exceptions import ArtistNotFoundException 10 | from src.models import SetenceFound 11 | from src.repository.words_in_songs import WordInSongs 12 | 13 | 14 | class WordInSongsController: 15 | @classmethod 16 | async def get(cls, artist: str, sentence: str) -> List[SetenceFound]: 17 | 18 | key = f"{artist}:{sentence}" 19 | randler_redis = RandlerRedis() 20 | 21 | logger.info(f"Buscando no redis a chave {key}") 22 | 23 | response = randler_redis.get_found_sentence_to_json(key) 24 | if response: 25 | logger.info(f"Dados encontrado no redis a chave {key}") 26 | return response 27 | 28 | try: 29 | word_songs = WordInSongs(artist, sentence) 30 | get_links_musics = word_songs.get_links_musics() 31 | 32 | with concurrent.futures.ThreadPoolExecutor() as executor: 33 | loop = asyncio.get_event_loop() 34 | 35 | tasks = [loop.run_in_executor(executor, word_songs.find_string_in_lyric, link) for link in 36 | get_links_musics] 37 | 38 | await asyncio.gather(*tasks) 39 | 40 | logger.info(f"Encontrado {len(word_songs.sentence_found_list())} vezes") 41 | 42 | except ArtistNotFoundException as not_found_artist: 43 | logger.warning(not_found_artist.__str__()) 44 | raise HTTPException(status_code=404, detail=not_found_artist.__str__()) 45 | 46 | except Exception as e: 47 | logger.error(e.__str__()) 48 | raise HTTPException(status_code=500, detail=e.__str__()) 49 | 50 | randler_redis.set_sentence_found( 51 | f"{artist}:{sentence}", 52 | word_songs.sentence_found_list() 53 | ) 54 | 55 | logger.info(f"Salvo no redis a chave {key}") 56 | return word_songs.sentence_found_list() 57 | -------------------------------------------------------------------------------- /src/models/__init__.py: -------------------------------------------------------------------------------- 1 | from src.models.artist_sentence_model import ArtistSentence 2 | from src.models.setence_found_model import SetenceFound 3 | -------------------------------------------------------------------------------- /src/models/artist_sentence_model.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class ArtistSentence(BaseModel): 5 | artist: str 6 | sentence: str 7 | -------------------------------------------------------------------------------- /src/models/setence_found_model.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class SetenceFound(BaseModel): 5 | music: str 6 | phase: str 7 | link: str 8 | -------------------------------------------------------------------------------- /src/repository/words_in_songs.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import requests 4 | from bs4 import BeautifulSoup 5 | from loguru import logger 6 | 7 | from src.Exceptions import ArtistNotFoundException, FindStringInLyricException 8 | from src.models import SetenceFound 9 | 10 | 11 | class WordInSongs: 12 | """ Classe responsável por buscar as músicas do artista e as frases que contenham a palavra """ 13 | __URL_VAGALUME = 'https://www.vagalume.com.br/' 14 | 15 | def __init__(self, artist: str, sentence: str): 16 | 17 | self.__artist = artist 18 | self.__sentence = sentence 19 | self.__soup_html: BeautifulSoup 20 | 21 | self.__setence_found_list: List[SetenceFound] = [] 22 | self.__links_musics: List[str] = [] 23 | 24 | # Página principal com todas as músicas dos artistas 25 | html = requests.get(f'{self.__URL_VAGALUME}/{self.__artist.replace(" ", "-").lower()}/') 26 | 27 | if html.status_code == 404: 28 | logger.warning(f"O artista '{self.__artist}' não foi encontrado.") 29 | raise ArtistNotFoundException(f"O artista '{self.__artist}' não foi encontrado.") 30 | 31 | # Estruturando dados como html 32 | self.__soup_html = BeautifulSoup(html.content, 'html.parser') 33 | 34 | # Captura o nome do artista 35 | self.__artist = str(self.__soup_html.title.string).replace(" - VAGALUME", "") 36 | 37 | def sentence_found_list(self) -> List[SetenceFound]: 38 | return self.__setence_found_list 39 | 40 | @property 41 | def artist(self): 42 | return self.__artist 43 | 44 | @property 45 | def sentence(self): 46 | return self.__sentence 47 | 48 | def get_links_musics(self) -> List[str]: 49 | """ Captura todos os links das músicas do artista """ 50 | 51 | try: 52 | # Procura a lista alfabetica e filtra todos as tags a 53 | for tag in self.__soup_html.find(id='alfabetMusicList').find_all('a'): 54 | # Por cada tag pega o conteúdo do atributo href 55 | path = tag.attrs['href'] 56 | # filtra os links para que não sej adicionado os links com #play no final 57 | if '#play' not in str(path): 58 | full_path = self.__URL_VAGALUME + path 59 | self.__links_musics.append(full_path) 60 | 61 | # Gera o link para acessar a música e adiciona na lista 62 | yield full_path 63 | except Exception: 64 | logger.warning(f"Erro ao capturar os links das músicas do artista '{self.__artist}'") 65 | raise ArtistNotFoundException(self.__artist) 66 | 67 | def find_string_in_lyric(self, link: str) -> None: 68 | """ Procura a ‘string’ em todas as letras das músicas do artista """ 69 | 70 | try: 71 | # faz a requisição até a página 72 | html_musica = requests.get(link) 73 | # Tratar o conteúdo recebido como um documento como uma estrutura de documento html 74 | soup = BeautifulSoup(html_musica.content, 'html.parser') 75 | 76 | page_music = [] 77 | try: 78 | 79 | for item in soup.find(id="lyrics").contents: 80 | page_music.append(item) 81 | 82 | except AttributeError: 83 | pass 84 | 85 | phases = [item.text for item in page_music if item.text != ''] 86 | musica = soup.find(id="lyricContent").find('h1').string 87 | 88 | for phase in phases: 89 | if not self.sentence.lower() in phase.lower(): 90 | continue 91 | 92 | if phase not in page_music: 93 | continue 94 | 95 | if phase in [item.phase for item in self.__setence_found_list]: 96 | continue 97 | 98 | if musica is None or phase is None or link is None: 99 | continue 100 | 101 | self.__setence_found_list.append(SetenceFound(music=musica, phase=phase, link=link)) 102 | 103 | except Exception: 104 | logger.warning(f"Erro ao encontrar a string na letra da música '{link}'") 105 | raise FindStringInLyricException(link) 106 | -------------------------------------------------------------------------------- /src/views/word_in_songs_view.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from fastapi import APIRouter 4 | 5 | from src.controllers.words_in_songs_controller import WordInSongsController 6 | from src.models import ArtistSentence, SetenceFound 7 | 8 | wis = WordInSongsController() 9 | 10 | wis_router = APIRouter() 11 | 12 | 13 | @wis_router.post("/wis", 14 | response_model=List[SetenceFound], 15 | status_code=200, 16 | summary="Busca a frase em todas as músicas do artista", 17 | description="Busca a frase em todas as músicas do artista") 18 | async def wsi(artist_sentence: ArtistSentence) -> List[SetenceFound]: 19 | artist = artist_sentence.artist 20 | sentence = artist_sentence.sentence 21 | 22 | return await wis.get(artist, sentence) 23 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericksonlopes/Words-In-Songs/68b776246f7a8c6950427a48ea05b186dc1e2460/tests/__init__.py -------------------------------------------------------------------------------- /tests/http/api_test.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:8000/api/v1/wis 2 | Content-Type: application/json 3 | 4 | { 5 | "artist": "cazuza", 6 | "sentence": "amor" 7 | } 8 | 9 | > {% 10 | client.test("Request executed successfully", function () { 11 | client.assert(response.status === 200, "response status is 200"); 12 | }) 13 | %} 14 | -------------------------------------------------------------------------------- /tests/test_class_wis.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import concurrent.futures 3 | from unittest import TestCase 4 | 5 | from src.Exceptions import ArtistNotFoundException 6 | from src.repository.words_in_songs import WordInSongs 7 | 8 | 9 | class TestClassWordInSongs(TestCase): 10 | 11 | def setUp(self): 12 | self.artist = "Cazuza" 13 | self.sentence = "amor" 14 | self.words = WordInSongs(self.artist, self.sentence) 15 | 16 | def test_find_string_in_lyrics(self): 17 | get_links_musics = self.words.get_links_musics() 18 | 19 | with concurrent.futures.ThreadPoolExecutor() as executor: 20 | loop = asyncio.get_event_loop() 21 | tasks = [loop.run_in_executor(executor, self.words.find_string_in_lyric, link) for link in 22 | get_links_musics] 23 | 24 | asyncio.gather(*tasks) 25 | 26 | self.assertTrue(self.words.sentence_found_list()) 27 | 28 | def test_get_links_music(self): 29 | self.assertTrue(self.words.get_links_musics()) 30 | 31 | def test_artist_not_found(self): 32 | with self.assertRaises(ArtistNotFoundException) as context: 33 | WordInSongs("testes_error", "amor") 34 | 35 | def test_sentence_not_found(self): 36 | if len(self.words.sentence_found_list()): 37 | self.fail("Sentence not found") 38 | 39 | def test_get_artist(self): 40 | self.assertEqual(self.words.artist.lower(), self.artist.lower()) 41 | 42 | def test_get_sentence(self): 43 | self.assertEqual(self.words.sentence.lower(), self.sentence.lower()) 44 | -------------------------------------------------------------------------------- /tests/test_fast_api.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from fastapi.testclient import TestClient 4 | 5 | from main import app 6 | 7 | client = TestClient(app) 8 | 9 | 10 | class TestClassWordInSongs(TestCase): 11 | def setUp(self): 12 | self.artist = "cazuza" 13 | self.sentence = "amor" 14 | 15 | def test_wis(self): 16 | response = client.post("/api/v1/wis", json={"artist": self.artist, "sentence": self.sentence}) 17 | 18 | self.assertEqual(response.status_code, 200) 19 | self.assertTrue(response.json()) 20 | 21 | def test_wis_not_found(self): 22 | response = client.post("/api/v1/wis", json={"artist": "not_found", "sentence": self.sentence}) 23 | 24 | self.assertEqual(response.status_code, 404) 25 | -------------------------------------------------------------------------------- /tests/test_redis.py: -------------------------------------------------------------------------------- 1 | from config import ConnectRedis, RandlerRedis 2 | from src.models import SetenceFound 3 | 4 | 5 | class TestRedis: 6 | def test_get_found_sentence_to_json(self): 7 | with ConnectRedis() as redis: 8 | redis.set("test", "test") 9 | assert redis.get("test") == b"test" 10 | redis.delete("test") 11 | assert redis.get("test") is None 12 | 13 | def test_set_sentence_found(self): 14 | randler = RandlerRedis() 15 | key = "test:test" 16 | randler.set_sentence_found(key, [SetenceFound(music="musica", phase="phase", link="link"), ]) 17 | assert randler.get_found_sentence_to_json(key) == [{"music": "musica", "phase": "phase", "link": "link"}] 18 | --------------------------------------------------------------------------------