From 34d333586746e92914f77a4cb09295c223599218 Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:30:32 +0200 Subject: [PATCH 1/8] Fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f6a903..bc4e041 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ client = LoadingApiClient(email="your@email.com", password="your_password") response = client.get_profile() ``` -It can also be used asyncrounously usage: +It can also be used asyncrounously: ```python from loading_sdk import AsyncLoadingApiClient From ec5bf12c9b3afd1dab5b4379b1cbd20bd2aee26c Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 13:52:18 +0200 Subject: [PATCH 2/8] Add beautifulsoup4 --- poetry.lock | 33 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index e367589..6ff0ba6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -70,6 +70,21 @@ python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "certifi" version = "2022.6.15" @@ -302,6 +317,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "sphinx" version = "5.1.1" @@ -507,7 +530,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "b1893540c1eea57c75fc89eca4ed0e2c0f24c86f8da88e0c2f107477f4adb5ef" +content-hash = "402cab824b070b7454b8bdfa1beae9ccbd72c6dd1d859f77fed3b98b50b2bdb0" [metadata.files] aiohttp = [ @@ -604,6 +627,10 @@ babel = [ {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, ] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] certifi = [ {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, @@ -848,6 +875,10 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] sphinx = [ {file = "Sphinx-5.1.1-py3-none-any.whl", hash = "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693"}, {file = "Sphinx-5.1.1.tar.gz", hash = "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89"}, diff --git a/pyproject.toml b/pyproject.toml index d3fd997..03596a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ packages = [ python = "^3.8" requests = "^2.28.1" aiohttp = "^3.8.1" +beautifulsoup4 = "^4.11.1" [tool.poetry.dev-dependencies] tox = "^3.25.1" From 3278bfbab763494904a5982bfe8945af1a554b65 Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 13:53:11 +0200 Subject: [PATCH 3/8] Add method for extracting data from about page --- loading_sdk/api.py | 74 ++++++++++++++++++++++++++++++++++++++ loading_sdk/async_api.py | 77 ++++++++++++++++++++++++++++++++++++++++ loading_sdk/settings.py | 1 + 3 files changed, 152 insertions(+) diff --git a/loading_sdk/api.py b/loading_sdk/api.py index eea9118..7d43bbe 100644 --- a/loading_sdk/api.py +++ b/loading_sdk/api.py @@ -1,16 +1,81 @@ +import json import math +import re import requests +from bs4 import BeautifulSoup from loading_sdk.settings import ( API_URL, API_VERSION, + BASE_URL, EDITORIAL_POST_TYPES, EDITORIAL_SORT, USER_AGENT, ) +class AboutPageExtractor: + def __init__(self): + about_page_source = self._get_source(f"{BASE_URL}/om") + main_script_url = self._extract_main_script_url(about_page_source) + main_script_source = self._get_source(f"{BASE_URL}/{main_script_url}") + about_script_url = self._get_about_script_url(main_script_source) + about_script_source = self._get_source(about_script_url) + + self.data = self._get_about_data(about_script_source) + + def _get_source(self, url): + headers = {"User-Agent": USER_AGENT} + response = requests.get(url, headers=headers) + + return response.text + + def _get_about_script_url(self, source_code): + chunk_urls = [] + + # Extracts the code with the javascript chunks. + p = re.compile("(static/js/).+?(?=\{)(.+?(?=\[)).+(.chunk.js)") + m = p.search(source_code) + + if m: + # Transform the code into valid JSON so the chunk ids can be stored in a python dict. + s = re.sub(r"([0-9]+?(?=:))", r'"\1"', m.group(2)) + chunk_ids = json.loads(s) + + for k, v in chunk_ids.items(): + chunk_url = f"{BASE_URL}/{m.group(1)}{k}.{v}{m.group(3)}" + chunk_urls.append(chunk_url) + + return chunk_urls[-1] + + def _get_about_data(self, source_code): + m = re.search("var.e=(.+?)(?=\.map).+a=(.+?)(?=\.map)", source_code) + + if m: + people = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(1)) + people = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', people) + people = people.replace('slags "vuxen p', "slags 'vuxen p") + people = people.replace('riktigt"-framtid', "riktigt'-framtid") + people = people.replace("\\n", "") + people = people.encode("utf-8").decode("unicode_escape") + + moderators = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(2)) + moderators = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', moderators) + moderators = moderators.replace("\\n", "") + moderators = moderators.encode("utf-8").decode("unicode_escape") + + about = {"people": json.loads(people), "moderators": json.loads(moderators)} + + return about + + def _extract_main_script_url(self, html): + soup = BeautifulSoup(html, "html.parser") + main_script = soup.find(src=re.compile("/static/js/main\.[0-9a-zA-Z]+\.js")) + + return main_script["src"][1:] + + class LoadingApiClient: """A client that allows python apps to easily communicate with the loading forums web api. @@ -456,3 +521,12 @@ def edit_thread(self, thread_id, message): thread_data["message"] = "Thread updated" return thread_data + + def get_about(self): + """Get about page data + + :rtype dict + """ + about_page = AboutPageExtractor() + + return about_page.data diff --git a/loading_sdk/async_api.py b/loading_sdk/async_api.py index 153b9aa..89dd5fd 100644 --- a/loading_sdk/async_api.py +++ b/loading_sdk/async_api.py @@ -1,9 +1,14 @@ +import json import math +import re + import aiohttp +from bs4 import BeautifulSoup from loading_sdk.settings import ( API_URL, API_VERSION, + BASE_URL, EDITORIAL_POST_TYPES, EDITORIAL_SORT, USER_AGENT, @@ -17,6 +22,68 @@ async def async_loading_api_client(email=None, password=None): return client +class AboutPageExtractor: + async def extract_about_data(self): + about_page_source = await self._get_source(f"{BASE_URL}/om") + main_script_url = self._extract_main_script_url(about_page_source) + main_script_source = await self._get_source(f"{BASE_URL}/{main_script_url}") + about_script_url = self._get_about_script_url(main_script_source) + about_script_source = await self._get_source(about_script_url) + + return self._get_about_data(about_script_source) + + async def _get_source(self, url): + headers = {"User-Agent": USER_AGENT} + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as response: + return await response.text() + + def _get_about_script_url(self, source_code): + chunk_urls = [] + + # Extracts the code with the javascript chunks. + p = re.compile("(static/js/).+?(?=\{)(.+?(?=\[)).+(.chunk.js)") + m = p.search(source_code) + + if m: + # Transform the code into valid JSON so the chunk ids can be stored in a python dict. + s = re.sub(r"([0-9]+?(?=:))", r'"\1"', m.group(2)) + chunk_ids = json.loads(s) + + for k, v in chunk_ids.items(): + chunk_url = f"{BASE_URL}/{m.group(1)}{k}.{v}{m.group(3)}" + chunk_urls.append(chunk_url) + + return chunk_urls[-1] + + def _get_about_data(self, source_code): + m = re.search("var.e=(.+?)(?=\.map).+a=(.+?)(?=\.map)", source_code) + + if m: + people = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(1)) + people = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', people) + people = people.replace('slags "vuxen p', "slags 'vuxen p") + people = people.replace('riktigt"-framtid', "riktigt'-framtid") + people = people.replace("\\n", "") + people = people.encode("utf-8").decode("unicode_escape") + + moderators = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(2)) + moderators = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', moderators) + moderators = moderators.replace("\\n", "") + moderators = moderators.encode("utf-8").decode("unicode_escape") + + about = {"people": json.loads(people), "moderators": json.loads(moderators)} + + return about + + def _extract_main_script_url(self, html): + soup = BeautifulSoup(html, "html.parser") + main_script = soup.find(src=re.compile("/static/js/main\.[0-9a-zA-Z]+\.js")) + + return main_script["src"][1:] + + class AsyncLoadingApiClient: """ An async client that allows python apps to easily communicate with the loading forums web api. @@ -490,3 +557,13 @@ async def edit_thread(self, thread_id, message): thread_data["message"] = "Thread updated" return thread_data + + async def get_about(self): + """Get about page data + + :rtype dict + """ + about_page = AboutPageExtractor() + about_data = await about_page.extract_about_data() + + return about_data \ No newline at end of file diff --git a/loading_sdk/settings.py b/loading_sdk/settings.py index 1047375..52a7a1c 100644 --- a/loading_sdk/settings.py +++ b/loading_sdk/settings.py @@ -1,3 +1,4 @@ +BASE_URL = "https://loading.se" API_URL = "https://api.loading.se" API_VERSION = "v1" USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0" From 24a460095f12a78a88a32b8d62fd165b842338a2 Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 13:54:07 +0200 Subject: [PATCH 4/8] Update readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index bc4e041..ce94753 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,8 @@ response = client.get_other(page=7) ```python response = client.get_editorials(page=2, post_type="review", sort="title") +``` + +```python +response = client.get_about() ``` \ No newline at end of file From e43623055df0343f816540b44d07e4a89027c08b Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:00:14 +0200 Subject: [PATCH 5/8] Refactor package --- loading_sdk/__init__.py | 4 +- loading_sdk/async_api/__init__.py | 5 ++ .../{async_api.py => async_api/client.py} | 70 +------------------ loading_sdk/async_api/extractors.py | 70 +++++++++++++++++++ loading_sdk/sync_api/__init__.py | 3 + loading_sdk/{api.py => sync_api/client.py} | 67 +----------------- loading_sdk/sync_api/extractors.py | 69 ++++++++++++++++++ 7 files changed, 152 insertions(+), 136 deletions(-) create mode 100644 loading_sdk/async_api/__init__.py rename loading_sdk/{async_api.py => async_api/client.py} (85%) create mode 100644 loading_sdk/async_api/extractors.py create mode 100644 loading_sdk/sync_api/__init__.py rename loading_sdk/{api.py => sync_api/client.py} (84%) create mode 100644 loading_sdk/sync_api/extractors.py diff --git a/loading_sdk/__init__.py b/loading_sdk/__init__.py index a3eda79..1c4e27c 100644 --- a/loading_sdk/__init__.py +++ b/loading_sdk/__init__.py @@ -1,4 +1,4 @@ -from loading_sdk.api import LoadingApiClient -from loading_sdk.async_api import async_loading_api_client as AsyncLoadingApiClient +from loading_sdk.sync_api import LoadingApiClient +from loading_sdk.async_api import AsyncLoadingApiClient __all__ = ["LoadingApiClient", "AsyncLoadingApiClient"] diff --git a/loading_sdk/async_api/__init__.py b/loading_sdk/async_api/__init__.py new file mode 100644 index 0000000..767390c --- /dev/null +++ b/loading_sdk/async_api/__init__.py @@ -0,0 +1,5 @@ +from loading_sdk.async_api.client import ( + async_loading_api_client as AsyncLoadingApiClient, +) + +__all__ = ["AsyncLoadingApiClient"] diff --git a/loading_sdk/async_api.py b/loading_sdk/async_api/client.py similarity index 85% rename from loading_sdk/async_api.py rename to loading_sdk/async_api/client.py index 89dd5fd..3510fd3 100644 --- a/loading_sdk/async_api.py +++ b/loading_sdk/async_api/client.py @@ -1,14 +1,10 @@ -import json import math -import re import aiohttp -from bs4 import BeautifulSoup - +from loading_sdk.async_api.extractors import AboutPageExtractor from loading_sdk.settings import ( API_URL, API_VERSION, - BASE_URL, EDITORIAL_POST_TYPES, EDITORIAL_SORT, USER_AGENT, @@ -22,68 +18,6 @@ async def async_loading_api_client(email=None, password=None): return client -class AboutPageExtractor: - async def extract_about_data(self): - about_page_source = await self._get_source(f"{BASE_URL}/om") - main_script_url = self._extract_main_script_url(about_page_source) - main_script_source = await self._get_source(f"{BASE_URL}/{main_script_url}") - about_script_url = self._get_about_script_url(main_script_source) - about_script_source = await self._get_source(about_script_url) - - return self._get_about_data(about_script_source) - - async def _get_source(self, url): - headers = {"User-Agent": USER_AGENT} - - async with aiohttp.ClientSession() as session: - async with session.get(url, headers=headers) as response: - return await response.text() - - def _get_about_script_url(self, source_code): - chunk_urls = [] - - # Extracts the code with the javascript chunks. - p = re.compile("(static/js/).+?(?=\{)(.+?(?=\[)).+(.chunk.js)") - m = p.search(source_code) - - if m: - # Transform the code into valid JSON so the chunk ids can be stored in a python dict. - s = re.sub(r"([0-9]+?(?=:))", r'"\1"', m.group(2)) - chunk_ids = json.loads(s) - - for k, v in chunk_ids.items(): - chunk_url = f"{BASE_URL}/{m.group(1)}{k}.{v}{m.group(3)}" - chunk_urls.append(chunk_url) - - return chunk_urls[-1] - - def _get_about_data(self, source_code): - m = re.search("var.e=(.+?)(?=\.map).+a=(.+?)(?=\.map)", source_code) - - if m: - people = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(1)) - people = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', people) - people = people.replace('slags "vuxen p', "slags 'vuxen p") - people = people.replace('riktigt"-framtid', "riktigt'-framtid") - people = people.replace("\\n", "") - people = people.encode("utf-8").decode("unicode_escape") - - moderators = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(2)) - moderators = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', moderators) - moderators = moderators.replace("\\n", "") - moderators = moderators.encode("utf-8").decode("unicode_escape") - - about = {"people": json.loads(people), "moderators": json.loads(moderators)} - - return about - - def _extract_main_script_url(self, html): - soup = BeautifulSoup(html, "html.parser") - main_script = soup.find(src=re.compile("/static/js/main\.[0-9a-zA-Z]+\.js")) - - return main_script["src"][1:] - - class AsyncLoadingApiClient: """ An async client that allows python apps to easily communicate with the loading forums web api. @@ -566,4 +500,4 @@ async def get_about(self): about_page = AboutPageExtractor() about_data = await about_page.extract_about_data() - return about_data \ No newline at end of file + return about_data diff --git a/loading_sdk/async_api/extractors.py b/loading_sdk/async_api/extractors.py new file mode 100644 index 0000000..bb61c05 --- /dev/null +++ b/loading_sdk/async_api/extractors.py @@ -0,0 +1,70 @@ +import json +import re + +import aiohttp +from bs4 import BeautifulSoup +from loading_sdk.settings import BASE_URL, USER_AGENT + + +class AboutPageExtractor: + async def extract_about_data(self): + about_page_source = await self._get_source(f"{BASE_URL}/om") + main_script_url = self._extract_main_script_url(about_page_source) + main_script_source = await self._get_source(f"{BASE_URL}/{main_script_url}") + about_script_url = self._get_about_script_url(main_script_source) + about_script_source = await self._get_source(about_script_url) + + return self._get_about_data(about_script_source) + + async def _get_source(self, url): + headers = {"User-Agent": USER_AGENT} + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as response: + return await response.text() + + def _get_about_script_url(self, source_code): + chunk_urls = [] + + # Extracts the code with the javascript chunks. + match = re.search(r"(static/js/).+?(?=\{)(.+?(?=\[)).+(.chunk.js)", source_code) + + if match: + # Transform the code into valid JSON so the chunk ids can be stored in a python dict. + file_name_values = re.sub(r"([0-9]+?(?=:))", r'"\1"', match.group(2)) + chunk_ids = json.loads(file_name_values) + + for key, value in chunk_ids.items(): + chunk_url = f"{BASE_URL}/{match.group(1)}{key}.{value}{match.group(3)}" + chunk_urls.append(chunk_url) + + return chunk_urls[-1] + + def _get_about_data(self, source_code): + match = re.search(r"var.e=(.+?)(?=\.map).+a=(.+?)(?=\.map)", source_code) + + if not match: + return None + + people = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', match.group(1)) + people = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', people) + people = people.replace('slags "vuxen p', "slags 'vuxen p") + people = people.replace('riktigt"-framtid', "riktigt'-framtid") + people = people.replace("\\n", "") + people = people.encode("utf-8").decode("unicode_escape") + + moderators = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', match.group(2)) + moderators = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', moderators) + moderators = moderators.replace("\\n", "") + moderators = moderators.encode("utf-8").decode("unicode_escape") + + return { + "people": json.loads(people), + "moderators": json.loads(moderators), + } + + def _extract_main_script_url(self, html): + soup = BeautifulSoup(html, "html.parser") + main_script = soup.find(src=re.compile(r"/static/js/main\.[0-9a-zA-Z]+\.js")) + + return main_script["src"][1:] diff --git a/loading_sdk/sync_api/__init__.py b/loading_sdk/sync_api/__init__.py new file mode 100644 index 0000000..704acc3 --- /dev/null +++ b/loading_sdk/sync_api/__init__.py @@ -0,0 +1,3 @@ +from loading_sdk.sync_api.client import LoadingApiClient + +__all__ = ["LoadingApiClient"] diff --git a/loading_sdk/api.py b/loading_sdk/sync_api/client.py similarity index 84% rename from loading_sdk/api.py rename to loading_sdk/sync_api/client.py index 7d43bbe..4e52c02 100644 --- a/loading_sdk/api.py +++ b/loading_sdk/sync_api/client.py @@ -1,79 +1,14 @@ -import json import math -import re import requests -from bs4 import BeautifulSoup - from loading_sdk.settings import ( API_URL, API_VERSION, - BASE_URL, EDITORIAL_POST_TYPES, EDITORIAL_SORT, USER_AGENT, ) - - -class AboutPageExtractor: - def __init__(self): - about_page_source = self._get_source(f"{BASE_URL}/om") - main_script_url = self._extract_main_script_url(about_page_source) - main_script_source = self._get_source(f"{BASE_URL}/{main_script_url}") - about_script_url = self._get_about_script_url(main_script_source) - about_script_source = self._get_source(about_script_url) - - self.data = self._get_about_data(about_script_source) - - def _get_source(self, url): - headers = {"User-Agent": USER_AGENT} - response = requests.get(url, headers=headers) - - return response.text - - def _get_about_script_url(self, source_code): - chunk_urls = [] - - # Extracts the code with the javascript chunks. - p = re.compile("(static/js/).+?(?=\{)(.+?(?=\[)).+(.chunk.js)") - m = p.search(source_code) - - if m: - # Transform the code into valid JSON so the chunk ids can be stored in a python dict. - s = re.sub(r"([0-9]+?(?=:))", r'"\1"', m.group(2)) - chunk_ids = json.loads(s) - - for k, v in chunk_ids.items(): - chunk_url = f"{BASE_URL}/{m.group(1)}{k}.{v}{m.group(3)}" - chunk_urls.append(chunk_url) - - return chunk_urls[-1] - - def _get_about_data(self, source_code): - m = re.search("var.e=(.+?)(?=\.map).+a=(.+?)(?=\.map)", source_code) - - if m: - people = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(1)) - people = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', people) - people = people.replace('slags "vuxen p', "slags 'vuxen p") - people = people.replace('riktigt"-framtid', "riktigt'-framtid") - people = people.replace("\\n", "") - people = people.encode("utf-8").decode("unicode_escape") - - moderators = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', m.group(2)) - moderators = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', moderators) - moderators = moderators.replace("\\n", "") - moderators = moderators.encode("utf-8").decode("unicode_escape") - - about = {"people": json.loads(people), "moderators": json.loads(moderators)} - - return about - - def _extract_main_script_url(self, html): - soup = BeautifulSoup(html, "html.parser") - main_script = soup.find(src=re.compile("/static/js/main\.[0-9a-zA-Z]+\.js")) - - return main_script["src"][1:] +from loading_sdk.sync_api.extractors import AboutPageExtractor class LoadingApiClient: diff --git a/loading_sdk/sync_api/extractors.py b/loading_sdk/sync_api/extractors.py new file mode 100644 index 0000000..4939a94 --- /dev/null +++ b/loading_sdk/sync_api/extractors.py @@ -0,0 +1,69 @@ +import json +import re + +import requests +from bs4 import BeautifulSoup +from loading_sdk.settings import BASE_URL, USER_AGENT + + +class AboutPageExtractor: + def __init__(self): + about_page_source = self._get_source(f"{BASE_URL}/om") + main_script_url = self._extract_main_script_url(about_page_source) + main_script_source = self._get_source(f"{BASE_URL}/{main_script_url}") + about_script_url = self._get_about_script_url(main_script_source) + about_script_source = self._get_source(about_script_url) + + self.data = self._get_about_data(about_script_source) + + def _get_source(self, url): + headers = {"User-Agent": USER_AGENT} + response = requests.get(url, headers=headers, timeout=10) + + return response.text + + def _get_about_script_url(self, source_code): + chunk_urls = [] + + # Extracts the code with the javascript chunks. + match = re.search(r"(static/js/).+?(?=\{)(.+?(?=\[)).+(.chunk.js)", source_code) + + if match: + # Transform the code into valid JSON so the chunk ids can be stored in a python dict. + file_name_values = re.sub(r"([0-9]+?(?=:))", r'"\1"', match.group(2)) + chunk_ids = json.loads(file_name_values) + + for key, value in chunk_ids.items(): + chunk_url = f"{BASE_URL}/{match.group(1)}{key}.{value}{match.group(3)}" + chunk_urls.append(chunk_url) + + return chunk_urls[-1] + + def _get_about_data(self, source_code): + match = re.search(r"var.e=(.+?)(?=\.map).+a=(.+?)(?=\.map)", source_code) + + if not match: + return None + + people = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', match.group(1)) + people = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', people) + people = people.replace('slags "vuxen p', "slags 'vuxen p") + people = people.replace('riktigt"-framtid', "riktigt'-framtid") + people = people.replace("\\n", "") + people = people.encode("utf-8").decode("unicode_escape") + + moderators = re.sub(r"(\{|\,)([a-z]+)(\:)", r'\1"\2"\3', match.group(2)) + moderators = re.sub(r"(.+)(')(.+)(')(.+)", r'\1"\3"\5', moderators) + moderators = moderators.replace("\\n", "") + moderators = moderators.encode("utf-8").decode("unicode_escape") + + return { + "people": json.loads(people), + "moderators": json.loads(moderators), + } + + def _extract_main_script_url(self, html): + soup = BeautifulSoup(html, "html.parser") + main_script = soup.find(src=re.compile(r"/static/js/main\.[0-9a-zA-Z]+\.js")) + + return main_script["src"][1:] From 8c657ca295ed00295cb4f121e9a5a3f160f1d437 Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:00:51 +0200 Subject: [PATCH 6/8] Refactor tests --- tests/test_api.py | 88 +++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 8fc812b..38bb902 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,7 +12,7 @@ def setUpClass(cls): cls.cookie_jar.set("jwt", "placeholder_token_1") cls.cookie_jar.set("refreshToken", "placeholder_token_2") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_authenticate_success(self, mock_requests): mock_response = MagicMock() mock_response.status_code = 200 @@ -30,7 +30,7 @@ def test_authenticate_success(self, mock_requests): self.assertEqual(response.get("code"), 200) self.assertEqual(response.get("cookies"), self.cookie_jar) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_authenticate_failure_incorrect_email_or_password(self, mock_requests): status_code = 401 expected_response = { @@ -54,7 +54,7 @@ def test_authenticate_failure_incorrect_email_or_password(self, mock_requests): self.assertEqual(response.get("code"), 401) self.assertEqual(response.get("message"), "Incorrect email or password") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_authenticate_failure_invalid_email(self, mock_requests): status_code = 400 expected_response = { @@ -86,7 +86,7 @@ def test_authenticate_failure_invalid_email(self, mock_requests): self.assertEqual(response.get("code"), 400) self.assertEqual(response.get("message"), "Validation error") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_authenticate_failure_empty_values(self, mock_requests): status_code = 400 expected_response = { @@ -127,8 +127,8 @@ def test_authenticate_failure_empty_values(self, mock_requests): self.assertEqual(response.get("code"), 400) self.assertEqual(response.get("message"), "Validation error") - @patch("loading_sdk.api.LoadingApiClient._authenticate") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.client.requests") def test_get_profile_success(self, mock_requests, mock_authenticate): status_code = 200 expected_response = { @@ -154,7 +154,7 @@ def test_get_profile_success(self, mock_requests, mock_authenticate): self.assertEqual(response.get("message"), "OK") self.assertDictEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_profile_failure(self, mock_requests): status_code = 401 expected_response = {"code": status_code, "message": "No auth token"} @@ -172,7 +172,7 @@ def test_get_profile_failure(self, mock_requests): self.assertEqual(response.get("code"), 401) self.assertEqual(response.get("message"), "No auth token") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_search_success(self, mock_requests): expected_response = { "posts": [ @@ -213,7 +213,7 @@ def test_search_success(self, mock_requests): self.assertEqual(response.get("message"), "OK") self.assertEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_search_success_no_results(self, mock_requests): expected_response = {"posts": [], "users": []} @@ -229,7 +229,7 @@ def test_search_success_no_results(self, mock_requests): self.assertEqual(response.get("message"), "No results") self.assertEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_search_failure_empty_query(self, mock_requests): status_code = 400 expected_response = { @@ -257,7 +257,7 @@ def test_search_failure_empty_query(self, mock_requests): self.assertEqual(response.get("message"), "Validation error") self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_post_failure_empty_post_id(self, mock_requests): expected_response = { "code": 404, @@ -277,7 +277,7 @@ def test_get_post_failure_empty_post_id(self, mock_requests): ) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_post_failure_post_does_not_exist(self, mock_requests): expected_response = {"code": 404, "message": "Post does not exist"} @@ -293,7 +293,7 @@ def test_get_post_failure_post_does_not_exist(self, mock_requests): self.assertEqual(response.get("message"), "Post does not exist") self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_post_success(self, mock_requests): status_code = 200 expected_response = { @@ -333,7 +333,7 @@ def test_get_post_success(self, mock_requests): self.assertEqual(response.get("message"), "OK") self.assertEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_thread_success(self, mock_requests): status_code = 200 expected_response = { @@ -390,7 +390,7 @@ def test_get_thread_success(self, mock_requests): self.assertEqual(response.get("message"), "OK") self.assertEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_thread_failure_empty_thread_id(self, mock_requests): status_code = 404 expected_response = { @@ -409,7 +409,7 @@ def test_get_thread_failure_empty_thread_id(self, mock_requests): self.assertEqual(response.get("code"), 404) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_thread_failure_not_a_thread_id(self, mock_requests): status_code = 200 expected_response = { @@ -452,7 +452,7 @@ def test_get_thread_failure_not_a_thread_id(self, mock_requests): self.assertEqual(response.get("code"), 200) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_thread_failure_does_not_exist(self, mock_requests): status_code = 404 expected_response = {"code": status_code, "message": "Post does not exist"} @@ -468,7 +468,7 @@ def test_get_thread_failure_does_not_exist(self, mock_requests): self.assertEqual(response.get("code"), 404) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_thread_failure_page_too_low(self, mock_requests): status_code = 200 expected_response = { @@ -514,7 +514,7 @@ def test_get_thread_failure_page_too_low(self, mock_requests): self.assertEqual(response.get("code"), 200) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_thread_failure_page_too_high(self, mock_requests): status_code = 200 expected_response = { @@ -561,7 +561,7 @@ def test_get_thread_failure_page_too_high(self, mock_requests): self.assertEqual(response.get("message"), "Page number too high") self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_games_success(self, mock_requests): status_code = 200 expected_response = { @@ -945,7 +945,7 @@ def test_get_games_success(self, mock_requests): self.assertDictEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_games_failure_page_too_low(self, mock_requests): status_code = 404 expected_response = { @@ -964,7 +964,7 @@ def test_get_games_failure_page_too_low(self, mock_requests): self.assertDictEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_games_failure_page_too_high(self, mock_requests): status_code = 404 expected_response = { @@ -983,7 +983,7 @@ def test_get_games_failure_page_too_high(self, mock_requests): self.assertDictEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_other_success(self, mock_requests): status_code = 200 expected_response = { @@ -1201,7 +1201,7 @@ def test_get_other_success(self, mock_requests): for thread in threads: self.assertEqual(thread.get("category"), "other") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_other_failure_page_too_low(self, mock_requests): status_code = 404 expected_response = { @@ -1220,7 +1220,7 @@ def test_get_other_failure_page_too_low(self, mock_requests): self.assertDictEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_other_failure_page_too_high(self, mock_requests): status_code = 404 expected_response = { @@ -1239,7 +1239,7 @@ def test_get_other_failure_page_too_high(self, mock_requests): self.assertDictEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_editorials_success(self, mock_requests): status_code = 200 expected_response = { @@ -1546,7 +1546,7 @@ def test_get_editorials_success(self, mock_requests): for thread in threads: self.assertEqual(thread.get("postType"), "update") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_editorials_failure_page_too_low(self, mock_requests): status_code = 404 expected_response = { @@ -1569,7 +1569,7 @@ def test_get_editorials_failure_page_too_low(self, mock_requests): self.assertDictEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_get_editorials_failure_page_too_high(self, mock_requests): status_code = 404 expected_response = { @@ -1592,8 +1592,8 @@ def test_get_editorials_failure_page_too_high(self, mock_requests): self.assertDictEqual(response, expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.client.requests") def test_edit_post_success(self, mock_requests, mock_authenticate): status_code = 200 expected_response = { @@ -1639,7 +1639,7 @@ def test_edit_post_success(self, mock_requests, mock_authenticate): self.assertEqual(response.get("code"), 200) self.assertDictEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_edit_post_failure_no_auth_token(self, mock_requests): status_code = 401 expected_response = {"code": status_code, "message": "No auth token"} @@ -1664,7 +1664,7 @@ def test_edit_post_failure_no_auth_token(self, mock_requests): self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_edit_post_failure_post_does_not_exist(self, mock_requests): status_code = 404 expected_response = {"code": status_code, "message": "Post does not exist"} @@ -1692,7 +1692,7 @@ def test_edit_post_failure_post_does_not_exist(self, mock_requests): self.assertEqual(response, expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") def test_edit_post_failure_empty_message(self, mock_authenticate): expected_response = { "code": 400, @@ -1717,7 +1717,7 @@ def test_edit_post_failure_empty_message(self, mock_authenticate): self.assertEqual(api._cookies, self.cookie_jar) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") def test_create_post_failure_empty_thread_id(self, mock_authenticate): expected_response = { "code": 400, @@ -1733,8 +1733,8 @@ def test_create_post_failure_empty_thread_id(self, mock_authenticate): self.assertEqual(api._cookies, self.cookie_jar) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.client.requests") def test_create_post_failure_thread_id_does_not_exist( self, mock_requests, mock_authenticate ): @@ -1757,7 +1757,7 @@ def test_create_post_failure_thread_id_does_not_exist( self.assertEqual(api._cookies, self.cookie_jar) self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_create_post_failure_no_auth_token(self, mock_requests): status_code = 401 expected_response = {"code": status_code, "message": "No auth token"} @@ -1774,8 +1774,8 @@ def test_create_post_failure_no_auth_token(self, mock_requests): self.assertEqual(response, expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.client.requests") def test_create_post_success(self, mock_requests, mock_authenticate): status_code = 201 expected_response = { @@ -1806,8 +1806,8 @@ def test_create_post_success(self, mock_requests, mock_authenticate): self.assertEqual(response.get("code"), 201) self.assertEqual(response.get("data"), expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.client.requests") def test_create_thread_success(self, mock_requests, mock_authenticate): status_code = 201 expected_response = { @@ -1864,8 +1864,8 @@ def test_create_thread_failure_invalid_post_type(self): self.assertEqual(response, expected_response) - @patch("loading_sdk.api.LoadingApiClient._authenticate") - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.LoadingApiClient._authenticate") + @patch("loading_sdk.sync_api.client.requests") def test_create_thread_failure_empty_title_or_message( self, mock_requests, mock_authenticate ): @@ -1904,7 +1904,7 @@ def test_create_thread_failure_empty_title_or_message( self.assertEqual(response, expected_response) - @patch("loading_sdk.api.requests") + @patch("loading_sdk.sync_api.client.requests") def test_create_thread_failure_no_auth_token(self, mock_requests): status_code = 401 expected_response = {"code": status_code, "message": "No auth token"} From a91e0e98b270261a7b4afd1bb1180161a423b869 Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:01:11 +0200 Subject: [PATCH 7/8] Fix tox.ini --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0c1805e..c2e6024 100644 --- a/tox.ini +++ b/tox.ini @@ -15,9 +15,10 @@ deps = flake8 mccabe pylint + bs4 commands = black --check --diff --verbose loading_sdk flake8 loading_sdk --max-complexity 10 --ignore E501 - pylint loading_sdk --disable=C0114,C0115,C0116,W0212,R0801 + pylint loading_sdk --disable=C0114,C0115,C0116,W0212,R0801,E0401,R0903 coverage run --source=loading_sdk --branch -m unittest -v coverage report -m \ No newline at end of file From cd8c345b44f8d4948801fb64b535383f6cb21ced Mon Sep 17 00:00:00 2001 From: Henrik Petersson <44243358+hnrkcode@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:05:44 +0200 Subject: [PATCH 8/8] Bump version to 0.2.2 --- docs/build/doctrees/environment.pickle | Bin 23707 -> 25339 bytes docs/build/doctrees/loading_sdk.doctree | Bin 160913 -> 167920 bytes docs/build/html/.buildinfo | 2 +- docs/build/html/_sources/loading_sdk.rst.txt | 4 +- .../html/_static/documentation_options.js | 2 +- docs/build/html/genindex.html | 92 ++++++------ docs/build/html/index.html | 2 +- docs/build/html/loading_sdk.html | 135 ++++++++++-------- docs/build/html/modules.html | 6 +- docs/build/html/objects.inv | Bin 556 -> 576 bytes docs/build/html/py-modindex.html | 6 +- docs/build/html/search.html | 2 +- docs/build/html/searchindex.js | 2 +- docs/source/conf.py | 2 +- docs/source/loading_sdk.rst | 4 +- pyproject.toml | 2 +- 16 files changed, 145 insertions(+), 116 deletions(-) diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle index c4cd9965449de7e56540c25e2ba110856ad74f5e..7dacb18e07172a70fcd674e22f8781b26ced20dd 100644 GIT binary patch literal 25339 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 EA8{PytN;K2 delta 6615 zcmcgxeQZ*6sss(=;g?>sB^(&~0>6wX$>X^Rw^s zi+OaK_~*WN?>!&C^SkGsd;R>;>)e;W%jr()zIeuaS$CP^8m_#|{b()M09W-U0KEeT zuIguS;7XP0)ogs+D5=s}^x=qqF0?T3i_RSuh1?^Nxp+7b6$M>rVJ>j=N{zK}wf%h7 zs#Semzk2PtFMQqG%fFE9U3sRKKbPvgw+p7L+H=YLv!V_9t1Ghv3*4?Q%W*Cq6NO@Y zEi70JkCAojn2kdc~>f`)> zu)`oFB5EQ!=Y%*7m5Xrh#;o%1jp){3lvKHGJLZx&D!dgRX+al0b3U<>=R^@DDA%=%gRFpPQqZUj;3|N|q(<_NvOHoZp{r#$> zDzmg{?opX#FR61-s#o`+lJasvD`(}ZNW5VEr+2gmSO4{4cyeQ*RR0SRfwXdAEjIB? zHN!ERDShQaT8h3z9Y~!4hp0-%Gk_mNR4d*fGTw$@)Lsr_EmhoEh_@Uz(qn&s#C8Z9 zVb9q;9pHN8A$t&eL)lWmPiWy*=YoX$6Xn9JmX|dNb}UfPT3$eI%tKl9x;&)g&p}&j zcRv}!+u?r1?x(qeowI1x9~X>=LU`Bq#Uj3?z)U<8o}<@pB8fjs==J*y6(zlM@Oo>A zy9KVcel7?{+rClr1{&eNiq`oz@hy3?CimhI{w*9D;jck=dz1aA^!;tx5(-E7ckul* z|1)e6%rEEz6Zu29*#2;F0x!ApPl`61kiF;+Er~)ASi4R2cq|C!4Ch6#ZzLw#gq$e; zlnVz$!2#Ccjm6|Bv5P`w+Uep1Zn)TR2VDy-vr7SgEC6qhnpUGQytA#$xH(N6E_p5a zk^`P9wZgNzEaW#Dk#?%3OTf4{7c4!wIaItEMt7IU*4J-JlXrsg(K6VvBO%-ZS9{76 zw%4a4A+lz8WpAzmP8;|-EV6y0E6qN5I?WK-r-xf3Nj$eZl6Z{icu>At#$C~0Du=m( zm>J&h%$G!);BtS7jM|=tzzLTJ%&tf@CbA^9%m-q=#ia;ILz}(QmZn6wT_OI<12vMz zt#H9pnhe^Ju1$iT99$>KZh^-Ji)3_lX`)V;nkbg}{D9;{1!h~tYEdYM$G4QPQ-IzI zr+Urc9yY2dcd0LN>OYsjPy<-aL4&#{`Sp^I-qZeE1ISo8hG~gY1=N z$u3N)x}@aGgGKmm24jy&ilLJvk;2%nAtF*IP4C3CsZDC9{FJG(rH5n-W2h*J$TC%s zN|H{%Bw@tzK=NmXkBFPq)&O;GM)#oj3!d81M{Lv7U7yGStP9b0pofa^-d-W zV7j+dmQ_%-!)Jh>3?xi;k%dnJGj^o#9~j&q0o#u2T`61Qwj{kqCX^dz8{8diC#&eB zA~Qpk+z0Tvp=Qa73)k8NKOgGl?m+4AUThbJ4`chA;Rv>ayXPgbMr6JWtb0n~ue%$h zp{UT$-IN|gVA7Ao>Bt0jFV#ev-4dkSs z6ytFriuBVVyHVDq8}Ab=vJ+= zN#AY$MGWX>!~SSAp`TvUu)12EjK)Wj%Rnp`nMZW)C~K5R*B20i4rRlLW$a!2{(_aLV>6prTCNB zNGT&1$s!A5$x19KGNUEAQC}L%XTes1eZr3W8IQjG1?)sEG>=;o^{cXy<;14ODVrxH zQIR?h9sGFA!jwTH>%+zR9u#DIYJA4X4+TeHl ze`IBJq*0XF4K##YP9TvQbjAH##Xe$ zzewcT$Xh#X_HR7VLX9h@1ViE%_W=1%LnLLKS4!~RsP?q@lsNOWh?nSqe`z)-@&h=C z|0k?yQool9ReJpM0mh;}g=Y|c>o4IvaL4a*HxYoO3HCiD@?E62Q|=Kj30FAhAw3U_ M&x~_k_|}Z)zXELbL;wH) diff --git a/docs/build/doctrees/loading_sdk.doctree b/docs/build/doctrees/loading_sdk.doctree index f2c0211049380b7e2ff490df578bbe2821b57339..db6f969817e59a59131fbf18871945e1c9c245e4 100644 GIT binary patch literal 167920 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.M@=Uw7Yi4(_h>$x9`p(7kCJQlCd!bYg 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 delta 24882 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 diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo index 2f95c55..c2e099c 100644 --- a/docs/build/html/.buildinfo +++ b/docs/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 2cdc8c0bb8e2a4831d8e8fe372441154 +config: e7a45ba62bf168cd4ebd435486d179fe tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_sources/loading_sdk.rst.txt b/docs/build/html/_sources/loading_sdk.rst.txt index 8dde1ee..301f258 100644 --- a/docs/build/html/_sources/loading_sdk.rst.txt +++ b/docs/build/html/_sources/loading_sdk.rst.txt @@ -7,7 +7,7 @@ Submodules loading\_sdk.api module ----------------------- -.. automodule:: loading_sdk.api +.. automodule:: loading_sdk.sync_api.client :members: :undoc-members: :show-inheritance: @@ -15,7 +15,7 @@ loading\_sdk.api module loading\_sdk.async_api module ----------------------------- -.. automodule:: loading_sdk.async_api +.. automodule:: loading_sdk.async_api.client :members: :undoc-members: :show-inheritance: diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js index dd1759d..01e77f0 100644 --- a/docs/build/html/_static/documentation_options.js +++ b/docs/build/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.2.1', + VERSION: '0.2.2', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html index 9aa78ac..fbf35b4 100644 --- a/docs/build/html/genindex.html +++ b/docs/build/html/genindex.html @@ -3,7 +3,7 @@ - Index — python-loading-sdk 0.2.1 documentation + Index — python-loading-sdk 0.2.2 documentation