diff --git a/.gitignore b/.gitignore index 7fd2914..d33d8b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ .cache .zipline +dev.py diff --git a/poetry.lock b/poetry.lock index 7dbf9cb..c60b93a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -121,6 +121,20 @@ files = [ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] +[[package]] +name = "argcomplete" +version = "3.2.3" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "argcomplete-3.2.3-py3-none-any.whl", hash = "sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c"}, + {file = "argcomplete-3.2.3.tar.gz", hash = "sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + [[package]] name = "astroid" version = "3.1.0" @@ -191,6 +205,52 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +[[package]] +name = "black" +version = "24.3.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "cairocffi" version = "1.6.1" @@ -450,6 +510,39 @@ webencodings = "*" doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] +[[package]] +name = "datamodel-code-generator" +version = "0.25.5" +description = "Datamodel Code Generator" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "datamodel_code_generator-0.25.5-py3-none-any.whl", hash = "sha256:3b62b42c8ebf2bb98cfbc24467b523c5b76780c585b72f4ac2fc1f1f576702ab"}, + {file = "datamodel_code_generator-0.25.5.tar.gz", hash = "sha256:545f897481a94781e32b3c26a452ce049320b091310729f7fc6fa780f6a87898"}, +] + +[package.dependencies] +argcomplete = ">=1.10,<4.0" +black = ">=19.10b0" +genson = ">=1.2.1,<2.0" +inflect = ">=4.1.0,<6.0" +isort = ">=4.3.21,<6.0" +jinja2 = ">=2.10.1,<4.0" +packaging = "*" +pydantic = [ + {version = ">=1.10.0,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.12\" and python_version < \"4.0\""}, + {version = ">=1.10.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.9.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, +] +pyyaml = ">=6.0.1" +toml = {version = ">=0.10.0,<1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +debug = ["PySnooper (>=0.4.1,<2.0.0)"] +graphql = ["graphql-core (>=3.2.3,<4.0.0)"] +http = ["httpx"] +validation = ["openapi-spec-validator (>=0.2.8,<0.7.0)", "prance (>=0.18.2)"] + [[package]] name = "defusedxml" version = "0.7.1" @@ -475,6 +568,41 @@ files = [ [package.extras] graph = ["objgraph (>=1.7.2)"] +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.1.1" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, + {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + [[package]] name = "frozenlist" version = "1.4.1" @@ -561,6 +689,16 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "genson" +version = "1.2.2" +description = "GenSON is a powerful, user-friendly JSON Schema generator." +optional = false +python-versions = "*" +files = [ + {file = "genson-1.2.2.tar.gz", hash = "sha256:8caf69aa10af7aee0e1a1351d1d06801f4696e005f06cedef438635384346a16"}, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -634,6 +772,21 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "inflect" +version = "5.6.2" +description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words" +optional = false +python-versions = ">=3.7" +files = [ + {file = "inflect-5.6.2-py3-none-any.whl", hash = "sha256:b45d91a4a28a4e617ff1821117439b06eaa86e2a4573154af0149e9be6687238"}, + {file = "inflect-5.6.2.tar.gz", hash = "sha256:aadc7ed73928f5e014129794bbac03058cca35d0a973a5fc4eb45c7fa26005f9"}, +] + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + [[package]] name = "isort" version = "5.13.2" @@ -1032,6 +1185,17 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" version = "23.2" @@ -1182,6 +1346,7 @@ files = [ [package.dependencies] annotated-types = ">=0.4.0" +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" @@ -1309,9 +1474,9 @@ files = [ astroid = ">=3.1.0,<=3.2.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=0.2", markers = "python_version < \"3.11\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -1624,6 +1789,17 @@ webencodings = ">=0.4" doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -1829,4 +2005,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10 || ^3.11 || ^3.12" -content-hash = "81bf41a2234efa4a9682bf29ea427b0e090c72c72134b7296cc135c4952199eb" +content-hash = "3cd54cc2b740040bab9d9bae7bc04645893711c141516a9182f6c68fcf6b128b" diff --git a/pyproject.toml b/pyproject.toml index 5dc59f9..291e010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ optional = true [tool.poetry.group.dev.dependencies] pylint = "^3.0.3" ruff = "^0.3.4" +datamodel-code-generator = "^0.25.5" [tool.poetry.group.docs] optional = true diff --git a/pyzipline/models.py b/pyzipline/models.py index ee7fed3..2eaf219 100644 --- a/pyzipline/models.py +++ b/pyzipline/models.py @@ -1,64 +1,67 @@ """This is a list of all the models used in PyZipline. They are used to represent the data returned from the Zipline API.""" -from typing import List, Dict, Optional from datetime import datetime -from pyzipline.utils import convert_str_to_datetime +from typing import Dict, List, Optional, Union + +from pydantic import BaseModel, Field -class File: +class File(BaseModel): """File object used for uploading files to Zipline Attributes: - created_at (datetime.datetime): Datetime object of when the file was created + createdAt (datetime.datetime): Datetime object of when the file was created id (int): ID of the file mimetype (str): String of the file's mimetype views (int): Integer of the number of views the file has name (str): String of the file's name size (int): Integer of the file's size in bytes favorite (bool): Boolean of whether the file is favorited - original_name (Optional[str]): String of the file's original name + originalName (Optional[str]): String of the file's original name url (Optional[str]): String of the file's URL - max_views (Optional[int]): Integer of the file's maximum number of views - expired_at (Optional[datetime]): Datetime object of when the file will expire + maxViews (Optional[int]): Integer of the file's maximum number of views + expiredAt (Optional[datetime]): Datetime object of when the file will expire thumbnail (Optional[str]): String of the file's thumbnail URL - folder_id (Optional[int]): Integer of the file's folder ID + folderId (Optional[int]): Integer of the file's folder ID """ - def __init__( - self, - createdAt: datetime, - id: int, # pylint: disable=redefined-builtin - mimetype: str, - views: int, - name: str, - size: int, - favorite: bool, - originalName: str = None, - url: str = None, - maxViews: int = None, - expiredAt: datetime = None, - thumbnail: str = None, - folderId: int = None, - **kwargs - ): - self.created_at = createdAt - self.id = id - self.mimetype = mimetype - self.views = views - self.name = name - self.size = size - self.favorite = favorite - self.original_name = originalName - self.url = url - self.max_views = maxViews - self.expired_at = expiredAt - self.thumbnail = thumbnail - self.folder_id = folderId - self.__dict__.update(kwargs) + createdAt: datetime + id: int + mimetype: str + views: int + name: str + size: int + favorite: bool + originalName: Optional[str] = None + url: Optional[str] = None + maxViews: Optional[int] = None + expiredAt: Optional[datetime] = None + thumbnail: Optional[str] = None + folderId: Optional[int] = None def __str__(self): return self.name +class Invite(BaseModel): + """Invite object used for managing invites -class Result: + Attributes: + id (int): Integer ID of the invite + code (str): String of the invite's code + createdAt (datetime): Datetime object of when the invite was created + expiresAt (datetime): Datetime object of when the invite will expire + used (bool): Boolean of whether the invite has been used + createdById (int): Integer ID of the user who created the invite + """ + id: int + code: str + createdAt: datetime + expiresAt: datetime + used: bool + createdById: int + + def __str__(self): + return self.code + +class Result(BaseModel): """Result returned from low-level RestAdapter Attributes: @@ -67,50 +70,44 @@ class Result: message (str = ''): Human readable result data (Union[List[Dict], Dict]): Python List of Dictionaries (or maybe just a single Dictionary on error) """ - def __init__(self, success: bool, status_code: int, message: str = '', data: List[Dict] = None): - self.success = success - self.status_code = status_code - self.message = message - self.data = data if data else {} + success: bool + status_code: int + message: str = '' + data: Union[List[Dict], Dict] = {} def __str__(self): - return f"{self.status_code}: {self.message}\n{self.data}" + return f"{self.status_code}: {self.message}" - -class Invite: - """Invite object used for managing invites +class TypesCountItem(BaseModel): + """Model used in [StatsData](.#pyzipline.models.StatsData) for storing the number of files with a specific mimetype Attributes: - id (int): Integer ID of the invite - code (str): String of the invite's code - created_at (datetime): Datetime object of when the invite was created - expires_at (datetime): Datetime object of when the invite will expire - used (bool): Boolean of whether the invite has been used - created_by_id (int): Integer ID of the user who created the invite + mimetype (str): String of the mimetype + count (int): Integer of the number of files with this mimetype """ - def __init__( - self, - id: int, # pylint: disable=redefined-builtin - code: str, - createdAt: str, - expiresAt: str, - used: bool, - createdById: int, - **kwargs - ): - self.id = id - self.code = code - self.created_at = convert_str_to_datetime(createdAt) - self.expires_at = convert_str_to_datetime(expiresAt) - self.used = used - self.created_by_id = createdById - self.__dict__.update(kwargs) + count: int + mimetype: str def __str__(self): - return self.code + return f"{self.mimetype}: {self.count}" -class Stats: - """Stats object used for retrieving stats + +class CountByUserItem(BaseModel): + """Model used in [StatsData](.#pyzipline.models.StatsData) for storing the number of files uploaded by a user + + Attributes: + username (str): String of the username + count (int): Integer of the number of files uploaded by this user + """ + count: int + username: str + + def __str__(self): + return f"{self.username}: {self.count}" + + +class StatsData(BaseModel): + """Stats data model Attributes: id (int): Integer ID of the stats @@ -124,225 +121,64 @@ class Stats: types_count (Optional[List[Mimetype]]): List of Mimetype objects count_by_user (Optional[List[CountByUser]]): List of CountByUser objects """ - def __init__( - self, - id: int, # pylint: disable=redefined-builtin - createdAt: datetime, - data: dict, - max_timestamp: Optional[datetime] = None - ): - self.id = id - self.createdAt = createdAt - self._data = data - self.max_timestamp = max_timestamp - self.size = self._data['size'] - self.size_num = self._data['size_num'] - self.count = self._data['count'] - self.count_users = self._data['count_users'] - self.views_count = self._data['views_count'] - self.types_count: list = self._data['types_count'] - self.count_by_user: list = self._data['count_by_user'] - if self.types_count is not None: - new_types_count = [] - for mimetype_entry in self.types_count: - if isinstance(mimetype_entry, dict): - m = self.Mimetype(**mimetype_entry) - new_types_count.append(m) - self.types_count = new_types_count - if self.count_by_user is not None: - new_count_by_user = [] - for count_by_user_entry in self.count_by_user: - if isinstance(count_by_user_entry, dict): - c = self.CountByUser(**count_by_user_entry) - new_count_by_user.append(c) - self.count_by_user = new_count_by_user + size: str + count: int + size_num: int + count_users: int + types_count: List[TypesCountItem] + views_count: int + count_by_user: List[CountByUserItem] - def __str__(self): - return str(self.id) - class Mimetype: - """Object used in [Stats](.#pyzipline.models.Stats) for storing the number of files with a specific mimetype - - Attributes: - mimetype (str): String of the mimetype - count (int): Integer of the number of files with this mimetype""" - def __init__( - self, - mimetype: str, - count: int - ): - self.mimetype = mimetype - self.count = count - - def __str__(self): - return f"{self.mimetype}: {self.count}" - - class CountByUser: - """Object used in [Stats](.#pyzipline.models.Stats) for storing the number of files uploaded by a user - - Attributes: - username (str): String of the username - count (int): Integer of the number of files uploaded by this user""" - def __init__( - self, - username: str, - count: int - ): - self.username = username - self.count = count - - def __str__(self): - return f"{self.username}: {self.count}" - -class OAuth: - """OAuth object used for managing OAuth +class Stats(BaseModel): + """Stats model Attributes: - id (int): Integer ID of the OAuth - provider (str): String of the OAuth's provider, one of 'DISCORD', 'GITHUB', 'GOOGLE' - user_id (int): Integer ID of the user who owns the OAuth - oauth_id (str): String of the OAuth's provider ID - username (str): String of the OAuth's connected account's username - token (str): String of the OAuth's access token - refresh (Optional[str]): String of the OAuth's refresh token + id (int): Integer ID of the stats + createdAt (datetime): Datetime object of when the stats were created + data (StatsData): StatsData object of the stats data + max_timestamp (Optional[datetime]): Datetime object of the maximum timestamp of the stats """ - def __init__( - self, - id: int, # pylint: disable=redefined-builtin - provider: str, - oauthId: int, - providerId: str, - username: str, - token: str, - refresh: Optional[str], - **kwargs - ): - self.id = id - self.provider = provider - self.oauth_id = oauthId - self.provider_id = providerId - self.username = username - self.token = token - self.refresh = refresh - self.__dict__.update(kwargs) - - def __str__(self): - return self.provider + id: int + createdAt: datetime + data: StatsData + max_timestamp: str = Field(default=None) -class User: - """Object containing user information +class Embed(BaseModel): + color: Optional[str] = None + title: Optional[str] = None + siteName: Optional[str] = None + description: Optional[str] = None - /// admonition | Contains Sensitive Information - type: danger - Please be mindful of how you use/store this object, as it contains sensitive information such as the user's token and OAuth/TOTP information. - /// - Attributes: - id (int): Integer ID of the user - uuid (str): String of the user's UUID - username (str): String of the user's username - avatar (Optional[str]): String of the user's avatar, base64 encoded - token (str): String of the user's token - administrator (bool): Boolean of whether the user is an administrator - super_admin (bool): Boolean of whether the user is a super administrator - system_theme (str): String of the user's system theme - embed (Embed): Embed object of the user's embed - totp_secret (Optional[str]): String of the user's TOTP secret - domains (List[str]): List of Strings of the user's domains - oauth (Optional[List[OAuth]]): List of [OAuth](.#pyzipline.models.OAuth) objects - ratelimit (Optional[datetime]): Datetime object of when the user's ratelimit expires - """ - def __init__( - self, - id: int, # pylint: disable=redefined-builtin - uuid: str, - username: str, - avatar: Optional[str], - token: str, - administrator: bool, - superAdmin: bool, - systemTheme: str, - embed: 'Embed', - totpSecret: Optional[str], - domains: List[str], - oauth: Optional[List['OAuth']] = None, - ratelimit: Optional[datetime] = None, - **kwargs - ): - self.id = id - self.uuid = uuid - self.username = username - self.avatar = avatar - self.token = token - self.administrator = administrator - self.super_admin = superAdmin - self.system_theme = systemTheme - self.embed = self.Embed(**embed) - self.totp_secret = totpSecret - self.domains = domains - self.oauth = oauth - self.ratelimit = ratelimit - self.__dict__.update(kwargs) - if self.oauth is not None: - for oauth_entry in self.oauth: - self.oauth.remove(oauth_entry) - o = OAuth(**oauth_entry) - self.oauth.append(o) +class User(BaseModel): + id: int + uuid: str + username: str + avatar: Optional[str] + token: str + administrator: bool + superAdmin: bool + systemTheme: str + embed: Embed + ratelimit: None + totpSecret: Optional[str] + domains: List[str] def __str__(self): return self.username - class Embed: - """Object containing a user's embed settings +class Versions(BaseModel): + stable: str + upstream: str + current: str - Attributes: - color (Optional[str]): String of the embed's color - title (Optional[str]): String of the embed's title - site_name (Optional[str]): String of the embed's site name - description (Optional[str]): String of the embed's description - """ - def __init__( - self, - color: str = None, - title: str = None, - siteName: str = None, - description: str = None, - **kwargs - ): - self.color = color - self.title = title - self.site_name = siteName - self.description = description - self.__dict__.update(kwargs) - def __str__(self): - if self.title is None: - return "None" - return self.title - -class Version: - """Object containing the current, stable, and upstream versions of Zipline - - Attributes: - is_upstream (bool): Boolean of whether the current version is upstream (`trunk` branch) - update_to_type (str): String of the type of update available, one of 'stable' or 'upstream' - stable (str): String of the stable version - upstream (str): String of the upstream version - current (str): String of the current version - """ - def __init__( - self, - isUpstream: bool, - updateToType: str, - versions: {dict} - ): - self.is_upstream = isUpstream - self.update_to_type = updateToType - self._versions = versions - self.stable = self._versions['stable'] - self.upstream = self._versions['upstream'] - self.current = self._versions['upstream'] +class Version(BaseModel): + isUpstream: bool + updateToType: str + versions: Versions def __str__(self): - return self.current + return self.versions.current diff --git a/pyzipline/rest_adapter.py b/pyzipline/rest_adapter.py index 5dc40cc..28055cc 100644 --- a/pyzipline/rest_adapter.py +++ b/pyzipline/rest_adapter.py @@ -8,6 +8,7 @@ from urllib3 import disable_warnings from pyzipline.exceptions import HTTPFailure, PyZiplineError from pyzipline.models import Result + class RestAdapter: """Constructor for RestAdapter diff --git a/pyzipline/zipline.py b/pyzipline/zipline.py index f7936df..54846db 100644 --- a/pyzipline/zipline.py +++ b/pyzipline/zipline.py @@ -1,17 +1,19 @@ """This module contains the ZiplineApi class, which is the main class used to interact with the Zipline API.""" -from datetime import datetime, timedelta -from typing import Union, List import logging +from datetime import datetime, timedelta +from typing import List, Union + +from pydantic import BaseModel, ConfigDict + +from pyzipline.exceptions import (FeatureDisabledError, Forbidden, NotFound, + PyZiplineError) +from pyzipline.models import File, Invite, Result, Stats, User, Version from pyzipline.rest_adapter import RestAdapter -from pyzipline.exceptions import PyZiplineError, FeatureDisabledError, Forbidden, NotFound -from pyzipline.models import User, File, Result, Invite, Stats, Version from pyzipline.utils import convert_datetime_to_str -# pylint: disable=not-a-mapping -class ZiplineApi: - """Represents an instance of the Zipline API. - All API requests should be made through this class. +class ZiplineApiConfig(BaseModel): + """Represents a configuration instance for the ZiplineApi class. Args: hostname (str): The hostname of your Zipline instance, WITHOUT https or http. @@ -20,15 +22,27 @@ class ZiplineApi: enforced_signing (bool): Normally set to True, but if having SSL/TLS cert validation issues, can turn off with False. logger (logging.Logger): If your app has a logger, pass it in here. """ + model_config = ConfigDict(arbitrary_types_allowed=True) + hostname: str + token: str = '' + ssl: bool = True + enforced_signing: bool = True + logger: logging.Logger = logging.getLogger(__name__) + +# pylint: disable=not-a-mapping +class ZiplineApi: + """Represents an instance of the Zipline API. + + All API requests should be made through this class. + + Args: + config (ZiplineApiConfig): Configuration object for the ZiplineApi class + """ def __init__( self, - hostname: str, - token: str = '', - ssl: bool = True, - enforced_signing: bool = True, - logger: logging.Logger = None + config: ZiplineApiConfig ): - self._rest_adapter = RestAdapter(hostname=hostname, token=token, ssl=ssl, enforced_signing=enforced_signing, logger=logger) + self._rest_adapter = RestAdapter(hostname=config.hostname, token=config.token, ssl=config.ssl, enforced_signing=config.enforced_signing, logger=config.logger) def create_invite(self, expiry: timedelta = timedelta(days=1), count: int = 1) -> Union[Invite, List[Invite]]: """Create an invite code @@ -302,11 +316,7 @@ class ZiplineApi: """ result = self._rest_adapter.get(endpoint="auth/invite") if result.status_code == 200: - invites = [] - for invite in result.data: - i = Invite(**invite) - invites.append(i) - return invites + return [Invite(**invite) for invite in result.data] if result.status_code == 401: raise Forbidden(result.message) if result.message == 'invites are disabled': @@ -417,11 +427,7 @@ class ZiplineApi: """ result = self._rest_adapter.get(endpoint="users") if result.status_code == 200: - users = [] - for user in result.data: - u = User(**user) - users.append(u) - return users + return [User(**user) for user in result.data] if result.status_code == 403: raise Forbidden(result.message) raise PyZiplineError(f"{result.status_code}: {result.message}\n{result.data}") @@ -445,7 +451,7 @@ class ZiplineApi: /// Args: - amount (int ): Number of stats to retrieve + amount (int): Number of stats to retrieve force_update (bool): Force the Zipline instance to update its statistics before returning them, requires administrator Raises: @@ -463,14 +469,7 @@ class ZiplineApi: else: result = self._rest_adapter.get(endpoint="stats", params={'amount': amount}) if result.status_code == 200: - if amount > 1: - stats_list = [] - for stats in result.data: - s = Stats(**stats) - stats_list.append(s) - return stats_list - data = result.data[0] if isinstance(result.data, list) else result.data - return Stats(**data) + return [Stats(**stats) for stats in result.data] if result.status_code in (401, 403): raise Forbidden(result.message) raise PyZiplineError(f"{result.status_code}: {result.message}\n{result.data}")