From 80c140e3ad19dc5219e0264786ba534add2737b6 Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Fri, 26 May 2023 14:39:34 +0200 Subject: [PATCH] Add Ruff; init tests; fix code style and type hints (#45) --- .github/workflows/black.yml | 4 +- .github/workflows/ruff.yml | 29 ++ .github/workflows/test.yml | 25 ++ .gitignore | 2 + atproto/car/__init__.py | 8 +- atproto/cbor/__init__.py | 4 +- atproto/cli/__init__.py | 38 +- atproto/codegen/__init__.py | 16 +- atproto/codegen/models/builder.py | 4 +- atproto/codegen/models/generator.py | 59 +-- atproto/codegen/namespaces/builder.py | 4 +- atproto/codegen/namespaces/generator.py | 20 +- atproto/exceptions.py | 2 +- atproto/firehose/__init__.py | 8 +- atproto/firehose/client.py | 57 +-- atproto/firehose/models.py | 53 +-- atproto/leb128/__init__.py | 4 +- atproto/lexicon/models.py | 2 +- atproto/lexicon/parser.py | 20 +- atproto/nsid/__init__.py | 20 +- atproto/uri/__init__.py | 21 +- atproto/xrpc_client/client/async_client.py | 2 +- atproto/xrpc_client/client/async_raw.py | 2 +- atproto/xrpc_client/client/base.py | 10 +- atproto/xrpc_client/client/client.py | 2 +- .../client/methods_mixin/session.py | 2 +- atproto/xrpc_client/client/raw.py | 2 +- atproto/xrpc_client/models/__init__.py | 344 +++++------------- atproto/xrpc_client/models/base.py | 2 +- atproto/xrpc_client/models/blob_ref.py | 2 +- atproto/xrpc_client/models/utils.py | 24 +- atproto/xrpc_client/namespaces/async_ns.py | 8 +- atproto/xrpc_client/namespaces/sync_ns.py | 8 +- atproto/xrpc_client/request.py | 40 +- poetry.lock | 105 +++++- pyproject.toml | 60 ++- test.py | 20 +- tests/__init__.py | 0 tests/test_lexicon_parser.py | 9 + tests/test_nsid.py | 17 + tests/test_uri.py | 14 + 41 files changed, 563 insertions(+), 510 deletions(-) create mode 100644 .github/workflows/ruff.yml create mode 100644 .github/workflows/test.yml create mode 100644 tests/__init__.py create mode 100644 tests/test_lexicon_parser.py create mode 100644 tests/test_nsid.py create mode 100644 tests/test_uri.py diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 048d2196..90689623 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -8,10 +8,10 @@ jobs: steps: - name: Checkout repository. - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Python. - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.7 diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 00000000..a8a02f6c --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,29 @@ +name: Ruff + +on: [ pull_request, push ] + +jobs: + ruff: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository. + uses: actions/checkout@v3 + + - name: Setup Python. + uses: actions/setup-python@v4 + with: + python-version: 3.7 + + - name: Install dependencies. + run: pip install black + + - name: Run linter check on package. + uses: chartboost/ruff-action@v1 + with: + src: "./atproto" + + - name: Run linter check on tests. + uses: chartboost/ruff-action@v1 + with: + src: "./tests" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..534d5190 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Unit Tests + +on: [ pull_request, push ] + +jobs: + unit_tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository. + uses: actions/checkout@v3 + + - name: Set up Python. + uses: actions/setup-python@v4 + with: + python-version: '3.7' + + - name: Setup Poetry. + uses: snok/install-poetry@v1 + + - name: Install dependencies. + run: poetry install + + - name: Run Tests. + run: poetry run pytest diff --git a/.gitignore b/.gitignore index 384c390c..757edfc1 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ coverage.xml # Sphinx documentation docs/_build/ + +.ruff_cache/ diff --git a/atproto/car/__init__.py b/atproto/car/__init__.py index 6aefcf7d..083f92c0 100644 --- a/atproto/car/__init__.py +++ b/atproto/car/__init__.py @@ -1,8 +1,8 @@ import typing as t from io import BytesIO -from .. import cbor, leb128 -from ..cid import CID +from atproto import cbor, leb128 +from atproto.cid import CID Blocks = t.Dict[CID, dict] @@ -12,12 +12,12 @@ class CAR: _CID_V1_BYTES_LEN = 36 - def __init__(self, root: str, blocks: Blocks): + def __init__(self, root: str, blocks: Blocks) -> None: self._root = root self._blocks = blocks @property - def root(self): + def root(self) -> str: """Get root.""" return self._root diff --git a/atproto/cbor/__init__.py b/atproto/cbor/__init__.py index 0418e603..06a3e79d 100644 --- a/atproto/cbor/__init__.py +++ b/atproto/cbor/__init__.py @@ -9,14 +9,14 @@ class _BytesReadCounter: _num_bytes_read = 0 - def __call__(self, _, num_bytes_read: int): + def __call__(self, _, num_bytes_read: int) -> None: self._num_bytes_read += num_bytes_read def __int__(self) -> int: return self._num_bytes_read -def decode_dag(data: DagCborData, *, allow_concat=False, callback=None) -> _dag_cbor.IPLDKind: +def decode_dag(data: DagCborData, *, allow_concat: bool = False, callback=None) -> _dag_cbor.IPLDKind: """Decodes and returns a single data item from the given data, with the DAG-CBOR codec. Args: diff --git a/atproto/cli/__init__.py b/atproto/cli/__init__.py index 1e9f4576..4df94d9e 100644 --- a/atproto/cli/__init__.py +++ b/atproto/cli/__init__.py @@ -1,3 +1,4 @@ +import typing as t from pathlib import Path import click @@ -11,18 +12,23 @@ class AliasedGroup(click.Group): """Ref: https://click.palletsprojects.com/en/8.1.x/advanced/""" - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: click.Context, cmd_name: str) -> t.Optional[click.Command]: rv = click.Group.get_command(self, ctx, cmd_name) if rv is not None: return rv + matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)] if not matches: return None - elif len(matches) == 1: + if len(matches) == 1: return click.Group.get_command(self, ctx, matches[0]) + ctx.fail(f'Too many matches: {", ".join(sorted(matches))}') + return None - def resolve_command(self, ctx, args): + def resolve_command( + self, ctx: click.Context, args: t.List[str] + ) -> t.Tuple[t.Optional[str], t.Optional[click.Command], t.List[str]]: # always return the full command name _, cmd, args = super().resolve_command(ctx, args) return cmd.name, cmd, args @@ -31,7 +37,7 @@ def resolve_command(self, ctx, args): @click.group(cls=AliasedGroup) @click.version_option(__version__) @click.pass_context -def atproto_cli(ctx: click.Context): +def atproto_cli(ctx: click.Context) -> None: """CLI of AT Protocol SDK for Python""" ctx.ensure_object(dict) @@ -39,7 +45,7 @@ def atproto_cli(ctx: click.Context): @atproto_cli.group(cls=AliasedGroup) @click.option('--lexicon-dir', type=click.Path(exists=True), default=None, help='Path to dir with .JSON lexicon files.') @click.pass_context -def gen(ctx: click.Context, lexicon_dir): +def gen(ctx: click.Context, lexicon_dir: t.Optional[str]) -> None: if lexicon_dir: lexicon_dir = Path(lexicon_dir) ctx.obj['lexicon_dir'] = lexicon_dir @@ -47,7 +53,7 @@ def gen(ctx: click.Context, lexicon_dir): @gen.command(name='all', help='Generated models, namespaces, and async clients with default configs.') @click.pass_context -def gen_all(_: click.Context): +def gen_all(_: click.Context) -> None: click.echo('Generating all:') click.echo('- models...') @@ -60,26 +66,26 @@ def gen_all(_: click.Context): click.echo('Done!') -def _gen_models(lexicon_dir=None, output_dir=None): - generate_models(lexicon_dir, output_dir) +def _gen_models(*args) -> None: + generate_models(*args) -def _gen_namespaces(lexicon_dir=None, output_dir=None, async_filename=None, sync_filename=None): - generate_namespaces(lexicon_dir, output_dir, async_filename, sync_filename) +def _gen_namespaces(*args) -> None: + generate_namespaces(*args) -def _gen_async_version(): +def _gen_async_version() -> None: gen_client('client.py', 'async_client.py') @gen.command(name='models') @click.option('--output-dir', type=click.Path(exists=True), default=None) @click.pass_context -def gen_models(ctx: click.Context, output_dir): +def gen_models(ctx: click.Context, output_dir: t.Optional[str]) -> None: click.echo('Generating models...') if output_dir: - # FIXME(MarshalX) + # FIXME(MarshalX): remove hardcoded imports output_dir = Path(output_dir) click.secho( "It doesn't work with '--output-dir' option very well because of hardcoded imports! Replace by yourself", @@ -96,7 +102,9 @@ def gen_models(ctx: click.Context, output_dir): @click.option('--async-filename', type=click.STRING, default=None, help='Should end with ".py".') @click.option('--sync-filename', type=click.STRING, default=None, help='Should end with ".py".') @click.pass_context -def gen_namespaces(ctx: click.Context, output_dir, async_filename, sync_filename): +def gen_namespaces( + ctx: click.Context, output_dir: t.Optional[str], async_filename: t.Optional[str], sync_filename: t.Optional[str] +) -> None: click.echo('Generating namespaces...') if output_dir: @@ -109,7 +117,7 @@ def gen_namespaces(ctx: click.Context, output_dir, async_filename, sync_filename @gen.command(name='async') @click.pass_context -def gen_async_version(_: click.Context): +def gen_async_version(_: click.Context) -> None: click.echo('Generating async clients...') _gen_async_version() click.echo('Done!') diff --git a/atproto/codegen/__init__.py b/atproto/codegen/__init__.py index d5162a3b..873e0698 100644 --- a/atproto/codegen/__init__.py +++ b/atproto/codegen/__init__.py @@ -6,7 +6,7 @@ from atproto.nsid import NSID DISCLAIMER = [ - '# THIS IS THE AUTO-GENERATED CODE. DON\'T EDIT IT BY HANDS!', + "# THIS IS THE AUTO-GENERATED CODE. DON'T EDIT IT BY HANDS!", '# Copyright (C) 2023 Ilya (Marshal) .', '# This file is part of Python atproto SDK. Licenced under MIT.', ] @@ -20,8 +20,11 @@ def format_code(filepath: Path) -> None: - subprocess.run(['black', '--quiet', filepath]) - subprocess.run(['isort', '--quiet', filepath]) + if not isinstance(filepath, Path): + return + + subprocess.run(['ruff', '--quiet', '--fix', filepath]) # noqa: S603, S607 + subprocess.run(['black', '--quiet', filepath]) # noqa: S603, S607 def append_code(filepath: Path, code: str) -> None: @@ -53,8 +56,7 @@ def get_import_path_old(nsid: NSID) -> str: def get_import_path(nsid: NSID) -> str: nsid_parts = nsid.segments[:-1] + camel_case_split(nsid.name) - alias_name = ''.join([p.capitalize() for p in nsid_parts]) - return alias_name + return ''.join([p.capitalize() for p in nsid_parts]) def convert_camel_case_to_snake_case(string: str) -> str: @@ -96,8 +98,8 @@ def get_sync_async_keywords(*, sync: bool) -> t.Tuple[str, str]: def capitalize_first_symbol(string: str) -> str: if string and string[0].islower(): - chars = [c for c in string[1:]] + chars = list(string[1:]) chars.insert(0, string[0].upper()) - string = ''.join(chars) + return ''.join(chars) return string diff --git a/atproto/codegen/models/builder.py b/atproto/codegen/models/builder.py index a4272961..d4d4b875 100644 --- a/atproto/codegen/models/builder.py +++ b/atproto/codegen/models/builder.py @@ -38,10 +38,10 @@ class _LexiconDir: dir_path: t.Optional[Path] - def __init__(self, default_path: Path = None): + def __init__(self, default_path: Path = None) -> None: self.dir_path = default_path - def set(self, path: Path): + def set(self, path: Path) -> None: self.dir_path = path def get(self) -> Path: diff --git a/atproto/codegen/models/generator.py b/atproto/codegen/models/generator.py index dd6a56e0..373ca5b7 100644 --- a/atproto/codegen/models/generator.py +++ b/atproto/codegen/models/generator.py @@ -12,9 +12,12 @@ capitalize_first_symbol, format_code, gen_description_by_camel_case_name, + get_file_path_parts, + get_import_path, + join_code, + write_code, ) from atproto.codegen import get_code_intent as _ -from atproto.codegen import get_file_path_parts, get_import_path, join_code, write_code from atproto.codegen.models import builder from atproto.exceptions import InvalidNsidError from atproto.lexicon import models @@ -50,7 +53,7 @@ def save_code_part(nsid: NSID, code: str) -> None: def _get_model_imports() -> str: - # TODO(MarshalX): isort can't delete unused imports. mb add ruff + # we are using ruff with F401 autofix to delete unused imports lines = [ 'from dataclasses import dataclass', 'import typing as t', @@ -70,7 +73,7 @@ def _get_model_imports() -> str: _NSID_WITH_IMPORTS = set() -def _save_code_import_if_not_exist(nsid) -> None: +def _save_code_import_if_not_exist(nsid: NSID) -> None: if nsid not in _NSID_WITH_IMPORTS: lines = [DISCLAIMER, _get_model_imports()] save_code(nsid, join_code(lines)) @@ -106,8 +109,7 @@ def _get_model_class_def(name: str, model_type: ModelType) -> str: def _get_optional_typehint(type_hint, *, optional: bool) -> str: if optional: return f't.Optional[{type_hint}] = None' - else: - return type_hint + return type_hint def _get_ref_typehint(nsid: NSID, field_type_def, *, optional: bool) -> str: @@ -137,7 +139,6 @@ def _resolve_nsid_ref(nsid: NSID, ref: str, *, local: bool = False) -> t.Tuple[s # FIXME(MarshalX): Is it works well? ;d return get_model_path(ref_nsid, 'Main'), def_name - # return get_model_path(ref_nsid, ref_nsid.name), def_name def _get_ref_union_typehint(nsid: NSID, field_type_def, *, optional: bool) -> str: @@ -154,7 +155,7 @@ def _get_ref_union_typehint(nsid: NSID, field_type_def, *, optional: bool) -> st def_names.append('t.Dict[str, t.Any]') def_names = ', '.join([f"'{name}'" for name in def_names]) - return _get_optional_typehint(f"t.Union[{def_names}]", optional=optional) + return _get_optional_typehint(f't.Union[{def_names}]', optional=optional) def _get_model_field_typehint(nsid: NSID, field_name: str, field_type_def, *, optional: bool) -> str: @@ -162,7 +163,7 @@ def _get_model_field_typehint(nsid: NSID, field_name: str, field_type_def, *, op if field_type == models.LexUnknown: # TODO(MarshalX): some of "unknown" types are well known... - return _get_optional_typehint(f"'base.RecordModelBase'", optional=optional) + return _get_optional_typehint("'base.RecordModelBase'", optional=optional) type_hint = _LEXICON_TYPE_TO_PRIMITIVE_TYPEHINT.get(field_type) if type_hint: @@ -179,7 +180,7 @@ def _get_model_field_typehint(nsid: NSID, field_name: str, field_type_def, *, op return _get_ref_union_typehint(nsid, field_type_def, optional=optional) if field_type is models.LexCidLink: - return _get_optional_typehint(f'CID', optional=optional) + return _get_optional_typehint('CID', optional=optional) if field_type is models.LexBytes: # CAR file containing relevant blocks @@ -218,7 +219,7 @@ def _get_model_docstring( nsid: t.Union[str, NSID], lex_object: t.Union[models.LexObject, models.LexXrpcParameters], model_type: ModelType ) -> str: model_desc = lex_object.description or '' - model_desc = f"{model_type.value} model for :obj:`{nsid}`. {model_desc}" + model_desc = f'{model_type.value} model for :obj:`{nsid}`. {model_desc}' doc_string = [f'{_(1)}"""{model_desc}"""', ''] @@ -356,7 +357,7 @@ def _generate_def_string(def_name: str, def_model: models.LexString) -> str: type_ = f'te.Literal[{known_values}]' lines = [ - f"{get_def_model_name(def_name)} = {type_}", + f'{get_def_model_name(def_name)} = {type_}', '', '', ] @@ -443,13 +444,21 @@ def _generate_record_type_database(lex_db: builder.LexDB) -> None: def _generate_ref_models(lex_db: builder.LexDB) -> None: for nsid, defs in lex_db.items(): definition = defs['main'] - if hasattr(definition, 'input') and definition.input and definition.input.schema: - if isinstance(definition.input.schema, models.LexRef): - save_code_part(nsid, _get_model_ref(nsid, definition.input.schema)) - - if hasattr(definition, 'output') and definition.output and definition.output.schema: - if isinstance(definition.output.schema, models.LexRef): - save_code_part(nsid, _get_model_ref(nsid, definition.output.schema)) + if ( + hasattr(definition, 'input') + and definition.input + and definition.input.schema + and isinstance(definition.input.schema, models.LexRef) + ): + save_code_part(nsid, _get_model_ref(nsid, definition.input.schema)) + + if ( + hasattr(definition, 'output') + and definition.output + and definition.output.schema + and isinstance(definition.output.schema, models.LexRef) + ): + save_code_part(nsid, _get_model_ref(nsid, definition.output.schema)) def _generate_init_files(root_package_path: Path) -> None: @@ -514,19 +523,21 @@ def _generate_empty_init_files(root_package_path: Path): write_code(root.joinpath('__init__.py'), DISCLAIMER) -def _generate_import_aliases(root_package_path: Path): - # is generates __init__.py file if models dir with aliases like this; - # from xrpc_client.models.app.bsky.actor import defs as AppBskyActorDefs +def _generate_import_aliases(root_package_path: Path) -> None: + # is generates __init__.py file if models dir with aliases like this: + # from xrpc_client.models.app.bsky.actor import defs as AppBskyActorDefs # noqa: ERA001 import_lines = [] - for root, dirs, files in os.walk(root_package_path): + for root, __, files in os.walk(root_package_path): root = Path(root) if root == root_package_path: continue for file in files: - if file.startswith('.') or file.startswith('__') or file.endswith('.pyc'): + if file.startswith(('.', '__', '.pyc')): + continue + if '.cpython-' in file: continue import_parts = root.parts[root.parts.index(_MODELS_OUTPUT_DIR.parent.name) :] @@ -540,7 +551,7 @@ def _generate_import_aliases(root_package_path: Path): write_code(_MODELS_OUTPUT_DIR.joinpath('__init__.py'), join_code(import_lines)) -def generate_models(lexicon_dir: t.Optional[Path] = None, output_dir: t.Optional[Path] = None): +def generate_models(lexicon_dir: t.Optional[Path] = None, output_dir: t.Optional[Path] = None) -> None: if lexicon_dir: builder.lexicon_dir.set(lexicon_dir) diff --git a/atproto/codegen/namespaces/builder.py b/atproto/codegen/namespaces/builder.py index 87c0c7c7..db512f8c 100644 --- a/atproto/codegen/namespaces/builder.py +++ b/atproto/codegen/namespaces/builder.py @@ -86,9 +86,7 @@ def build_namespace_tree(lexicons: t.List[LexiconDoc]) -> dict: def build_namespaces(lexicon_dir=None) -> dict: lexicons = lexicon_parse_dir(lexicon_dir) - namespace_tree = build_namespace_tree(lexicons) - - return namespace_tree + return build_namespace_tree(lexicons) if __name__ == '__main__': diff --git a/atproto/codegen/namespaces/generator.py b/atproto/codegen/namespaces/generator.py index b3eb89b1..88cf0d35 100644 --- a/atproto/codegen/namespaces/generator.py +++ b/atproto/codegen/namespaces/generator.py @@ -9,15 +9,13 @@ convert_camel_case_to_snake_case, format_code, gen_description_by_camel_case_name, -) -from atproto.codegen import get_code_intent as _ -from atproto.codegen import ( get_import_path, get_sync_async_keywords, join_code, sort_dict_by_key, write_code, ) +from atproto.codegen import get_code_intent as _ from atproto.codegen.namespaces.builder import MethodInfo, RecordInfo, build_namespaces from atproto.lexicon.models import ( LexDefinitionType, @@ -68,7 +66,7 @@ def _get_sub_namespaces_block(sub_namespaces: dict) -> str: lines = [] sub_namespaces = sort_dict_by_key(sub_namespaces) - for sub_namespace in sub_namespaces.keys(): + for sub_namespace in sub_namespaces: lines.append( f"{_(1)}{sub_namespace}: '{get_namespace_name(sub_namespace)}' = field(default_factory=DefaultNamespace)" ) @@ -77,10 +75,10 @@ def _get_sub_namespaces_block(sub_namespaces: dict) -> str: def _get_post_init_method(sub_namespaces: dict) -> str: - lines = [f'{_(1)}def __post_init__(self):'] + lines = [f'{_(1)}def __post_init__(self) -> None:'] sub_namespaces = sort_dict_by_key(sub_namespaces) - for sub_namespace in sub_namespaces.keys(): + for sub_namespace in sub_namespaces: lines.append(f'{_(2)}self.{sub_namespace} = {get_namespace_name(sub_namespace)}(self._client)') # TODO(MarshalX): add support for records @@ -164,10 +162,10 @@ def _override_arg_line(name: str, model_name: str) -> str: if isinstance(method_info.definition, LexXrpcProcedure): method_name = 'invoke_procedure' - lines.append(f"{_(2)}response = {c}self._client.{method_name}({invoke_args_str})") + lines.append(f'{_(2)}response = {c}self._client.{method_name}({invoke_args_str})') return_type, __ = _get_namespace_method_return_type(method_info) - lines.append(f"{_(2)}return get_response_model(response, {return_type})") + lines.append(f'{_(2)}return get_response_model(response, {return_type})') return join_code(lines) @@ -274,7 +272,7 @@ def _get_namespace_method_signature(method_info: MethodInfo, *, sync: bool) -> s if is_model: return_type = f"'{return_type}'" - return f"{_(1)}{d}def {name}({args}) -> {return_type}:" + return f'{_(1)}{d}def {name}({args}) -> {return_type}:' def _get_namespace_methods_block(methods_info: t.List[MethodInfo], sync: bool) -> str: @@ -310,9 +308,7 @@ def _generate_namespace_in_output(namespace_tree: t.Union[dict, list], output: t if isinstance(sub_node, list): output.append(_get_namespace_class_def(node_name)) - # TODO(MarshalX): implement later - # records = [info for info in sub_node if isinstance(info, RecordInfo)] - # output.append(_get_namespace_records_block(records)) + # TODO(MarshalX): gen namespace by RecordInfo later # TODO(MarshalX): generate namespace record classes! methods = [info for info in sub_node if isinstance(info, MethodInfo)] diff --git a/atproto/exceptions.py b/atproto/exceptions.py index f0478633..165c5e63 100644 --- a/atproto/exceptions.py +++ b/atproto/exceptions.py @@ -49,7 +49,7 @@ class ModelFieldNotFoundError(ModelError): class RequestErrorBase(AtProtocolError): - def __init__(self, response: t.Optional['Response'] = None): + def __init__(self, response: t.Optional['Response'] = None) -> None: self.response: 'Response' = response diff --git a/atproto/firehose/__init__.py b/atproto/firehose/__init__.py index 7485bded..78a88a04 100644 --- a/atproto/firehose/__init__.py +++ b/atproto/firehose/__init__.py @@ -73,13 +73,13 @@ def parse_subscribe_labels_message(message: 'MessageFrame') -> SubscribeLabelsMe class FirehoseSubscribeReposClient(FirehoseClient): - def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoSyncSubscribeRepos.Params']] = None): + def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoSyncSubscribeRepos.Params']] = None) -> None: params = get_or_create_model(params, models.ComAtprotoSyncSubscribeRepos.Params) super().__init__(method='com.atproto.sync.subscribeRepos', params=params) class AsyncFirehoseSubscribeReposClient(AsyncFirehoseClient): - def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoSyncSubscribeRepos.Params']] = None): + def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoSyncSubscribeRepos.Params']] = None) -> None: params = get_or_create_model(params, models.ComAtprotoSyncSubscribeRepos.Params) super().__init__(method='com.atproto.sync.subscribeRepos', params=params) @@ -88,12 +88,12 @@ def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoSyncSubscr class FirehoseSubscribeLabelsClient(FirehoseClient): - def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoLabelSubscribeLabels']] = None): + def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoLabelSubscribeLabels']] = None) -> None: params = get_or_create_model(params, models.ComAtprotoLabelSubscribeLabels.Params) super().__init__(method='com.atproto.label.subscribeLabels', params=params) class AsyncFirehoseSubscribeLabelsClient(AsyncFirehoseClient): - def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoLabelSubscribeLabels']] = None): + def __init__(self, params: t.Optional[t.Union[dict, 'models.ComAtprotoLabelSubscribeLabels']] = None) -> None: params = get_or_create_model(params, models.ComAtprotoLabelSubscribeLabels.Params) super().__init__(method='com.atproto.label.subscribeLabels', params=params) diff --git a/atproto/firehose/client.py b/atproto/firehose/client.py index b4e55192..9bd7212e 100644 --- a/atproto/firehose/client.py +++ b/atproto/firehose/client.py @@ -20,6 +20,8 @@ from atproto.xrpc_client.models.common import XrpcError if t.TYPE_CHECKING: + from httpx_ws import AsyncWebSocketSession, WebSocketSession + from atproto.firehose.models import MessageFrame _BASE_WEBSOCKET_URL = 'https://bsky.social/xrpc' @@ -40,23 +42,22 @@ def _build_websocket_url(method: str, base_url: t.Optional[str] = None) -> str: def _handle_firehose_error_or_stop(exception: Exception) -> bool: """Returns if the connection should be properly be closed or reraise exception.""" - print(exception) if isinstance(exception, WebSocketDisconnect): if exception.code == 1000: return True - elif exception.code in {1001, 1002, 1003}: + if exception.code in {1001, 1002, 1003}: return False - elif isinstance(exception, WebSocketNetworkError): + if isinstance(exception, WebSocketNetworkError): return False - elif isinstance(exception, httpx.NetworkError): + if isinstance(exception, httpx.NetworkError): return False - elif isinstance(exception, httpx.TimeoutException): + if isinstance(exception, httpx.TimeoutException): return False - elif isinstance(exception, WebSocketInvalidTypeReceived): + if isinstance(exception, WebSocketInvalidTypeReceived): raise FirehoseError from exception - elif isinstance(exception, WebSocketUpgradeError): + if isinstance(exception, WebSocketUpgradeError): raise FirehoseError from exception - elif isinstance(exception, FirehoseError): + if isinstance(exception, FirehoseError): raise exception raise FirehoseError from exception @@ -69,8 +70,8 @@ def __init__( base_url: t.Optional[str] = None, params: t.Optional[t.Dict[str, t.Any]] = None, *, - async_version=False, - ): + async_version: bool = False, + ) -> None: self._params = params self._url = _build_websocket_url(method, base_url) @@ -82,7 +83,9 @@ def __init__( self._on_message_callback: t.Optional[t.Union[OnMessageCallback, AsyncOnMessageCallback]] = None self._on_callback_error_callback: t.Optional[OnCallbackErrorCallback] = None - def _get_client(self): + def _get_client( + self, + ) -> t.Union[t.AsyncGenerator['AsyncWebSocketSession', None], t.Generator['WebSocketSession', None, None]]: if self._async_version: return aconnect_ws(self._url, params=self._params) @@ -90,7 +93,7 @@ def _get_client(self): def _get_reconnection_delay(self) -> int: base_sec = 2**self._reconnect_no - rand_sec = random.uniform(-0.5, 0.5) + rand_sec = random.uniform(-0.5, 0.5) # noqa: S311 return min(base_sec, self._max_reconnect_delay_sec) + rand_sec @@ -98,7 +101,7 @@ def _process_raw_frame(self, data: bytes) -> None: frame = Frame.from_bytes(data) if frame.is_error: raise FirehoseError(XrpcError(frame.body.error, frame.body.message)) - elif frame.is_message: + if frame.is_message: self._process_message_frame(frame) else: raise FirehoseError('Unknown frame type') @@ -117,7 +120,7 @@ def start( Returns: :obj:`None` """ - raise NotImplemented + raise NotImplementedError def stop(self) -> None: """Unsubscribe and stop Firehose client. @@ -125,14 +128,16 @@ def stop(self) -> None: Returns: :obj:`None` """ - raise NotImplemented + raise NotImplementedError def _process_message_frame(self, frame: 'MessageFrame') -> None: - raise NotImplemented + raise NotImplementedError class _WebsocketClient(_WebsocketClientBase): - def __init__(self, method: str, base_url: t.Optional[str] = None, params: t.Optional[t.Dict[str, t.Any]] = None): + def __init__( + self, method: str, base_url: t.Optional[str] = None, params: t.Optional[t.Dict[str, t.Any]] = None + ) -> None: super().__init__(method, base_url, params) self._stop_lock = threading.Lock() @@ -140,11 +145,11 @@ def __init__(self, method: str, base_url: t.Optional[str] = None, params: t.Opti def _process_message_frame(self, frame: 'MessageFrame') -> None: try: self._on_message_callback(frame) - except Exception as e: # noqa + except Exception as e: # noqa: BLE001 if self._on_callback_error_callback: try: self._on_callback_error_callback(e) - except Exception: # noqa + except: # noqa traceback.print_exc() else: traceback.print_exc() @@ -168,7 +173,7 @@ def start( while not self._stop_lock.locked(): raw_frame = client.receive_bytes() self._process_raw_frame(raw_frame) - except Exception as e: + except Exception as e: # noqa: BLE001 self._reconnect_no += 1 should_stop = _handle_firehose_error_or_stop(e) @@ -177,13 +182,15 @@ def start( self._stop_lock.release() - def stop(self): + def stop(self) -> None: if not self._stop_lock.locked(): self._stop_lock.acquire() class _AsyncWebsocketClient(_WebsocketClientBase): - def __init__(self, method: str, base_url: t.Optional[str] = None, params: t.Optional[t.Dict[str, t.Any]] = None): + def __init__( + self, method: str, base_url: t.Optional[str] = None, params: t.Optional[t.Dict[str, t.Any]] = None + ) -> None: super().__init__(method, base_url, params, async_version=True) self._loop = asyncio.get_event_loop() @@ -201,7 +208,7 @@ def _on_message_callback_done(self, task: asyncio.Task) -> None: try: self._on_callback_error_callback(task.exception()) - except Exception: # noqa + except: # noqa traceback.print_exc() def _process_message_frame(self, frame: 'MessageFrame') -> None: @@ -228,7 +235,7 @@ async def start( while not self._stop_lock.locked(): raw_frame = await client.receive_bytes() self._process_raw_frame(raw_frame) - except Exception as e: + except Exception as e: # noqa: BLE001 self._reconnect_no += 1 should_stop = _handle_firehose_error_or_stop(e) @@ -237,7 +244,7 @@ async def start( self._stop_lock.release() - async def stop(self): + async def stop(self) -> None: if not self._stop_lock.locked(): await self._stop_lock.acquire() diff --git a/atproto/firehose/models.py b/atproto/firehose/models.py index 0f33cbda..66053494 100644 --- a/atproto/firehose/models.py +++ b/atproto/firehose/models.py @@ -14,7 +14,7 @@ class FrameType(Enum): ERROR = -1 @classmethod - def has_value(cls, value) -> bool: + def has_value(cls, value: int) -> bool: return value in cls._value2member_map_ @@ -45,6 +45,32 @@ class ErrorFrameBody: message: Optional[str] = None #: Description of the error. +def parse_frame_header(raw_header: dict) -> FrameHeader: + try: + header_op = int(raw_header.get('op', 0)) + if not FrameType.has_value(header_op): + raise FirehoseError('Invalid frame type') + + frame_type = FrameType(header_op) + if frame_type is FrameType.MESSAGE: + return get_or_create_model(raw_header, MessageFrameHeader) + return get_or_create_model(raw_header, ErrorFrameHeader) + except (ValueError, AtProtocolError) as e: + raise FirehoseError('Invalid frame header') from e + + +def parse_frame(header: FrameHeader, raw_body: dict) -> Union['ErrorFrame', 'MessageFrame']: + try: + if isinstance(header, ErrorFrameHeader): + body = get_or_create_model(raw_body, ErrorFrameBody) + return ErrorFrame(header, body) + if isinstance(header, MessageFrameHeader): + return MessageFrame(header, raw_body) + raise FirehoseError('Invalid frame type') + except AtProtocolError as e: + raise FirehoseError('Invalid frame body') from e + + @dataclass class Frame: """Firehose base frame.""" @@ -100,29 +126,8 @@ def from_bytes(data: Union[bytes, bytearray]) -> Union['MessageFrame', 'ErrorFra if raw_body is None: raise FirehoseError('Frame body not found') - try: - header_op = int(raw_header.get('op', 0)) - if not FrameType.has_value(header_op): - raise FirehoseError('Invalid frame type') - - frame_type = FrameType(header_op) - if frame_type is FrameType.MESSAGE: - header = get_or_create_model(raw_header, MessageFrameHeader) - else: - header = get_or_create_model(raw_header, ErrorFrameHeader) - except (ValueError, AtProtocolError): - raise FirehoseError('Invalid frame header') - - try: - if isinstance(header, ErrorFrameHeader): - body = get_or_create_model(raw_body, ErrorFrameBody) - return ErrorFrame(header, body) - elif isinstance(header, MessageFrameHeader): - return MessageFrame(header, raw_body) - else: - raise FirehoseError('Invalid frame type') - except AtProtocolError: - raise FirehoseError('Invalid frame body') + header = parse_frame_header(raw_header) + return parse_frame(header, raw_body) @dataclass diff --git a/atproto/leb128/__init__.py b/atproto/leb128/__init__.py index c65660e3..aa9bdd80 100644 --- a/atproto/leb128/__init__.py +++ b/atproto/leb128/__init__.py @@ -16,7 +16,9 @@ class _U: @staticmethod def encode(i: int) -> bytearray: """Encode the int i using unsigned leb128 and return the encoded bytearray.""" - assert i >= 0 + if i < 0: + raise ValueError + r = [] while True: byte = i & 0x7F diff --git a/atproto/lexicon/models.py b/atproto/lexicon/models.py index c3d4125f..3adb4674 100644 --- a/atproto/lexicon/models.py +++ b/atproto/lexicon/models.py @@ -14,7 +14,7 @@ class LexDefinitionType(Enum): SUBSCRIPTION = 'subscription' PARAMS = 'params' - TOKEN = 'token' + TOKEN = 'token' # noqa: S105 OBJECT = 'object' BLOB = 'blob' diff --git a/atproto/lexicon/parser.py b/atproto/lexicon/parser.py index 0ae19df8..62f55022 100644 --- a/atproto/lexicon/parser.py +++ b/atproto/lexicon/parser.py @@ -46,8 +46,8 @@ def _lex_definition_type_hook(data: dict) -> models.LexDefinition: try: definition_class = _LEX_DEFINITION_TYPE_TO_CLASS[models.LexDefinitionType(data['type'])] - except (ValueError, KeyError): - raise UnknownDefinitionTypeError('Unknown definition type ' + data['type']) + except (ValueError, KeyError) as e: + raise UnknownDefinitionTypeError('Unknown definition type ' + data['type']) from e return lexicon_parse(data, definition_class) @@ -55,8 +55,8 @@ def _lex_definition_type_hook(data: dict) -> models.LexDefinition: def _lex_primitive_type_hook(data: dict) -> models.LexPrimitive: try: primitive_class = _LEX_PRIMITIVE_TYPE_TO_CLASS[models.LexPrimitiveType(data['type'])] - except (ValueError, KeyError): - raise UnknownPrimitiveTypeError('Unknown primitive type ' + data['type']) + except (ValueError, KeyError) as e: + raise UnknownPrimitiveTypeError('Unknown primitive type ' + data['type']) from e return lexicon_parse(data, primitive_class) @@ -64,8 +64,10 @@ def _lex_primitive_type_hook(data: dict) -> models.LexPrimitive: _TYPE_HOOKS = {models.LexDefinition: _lex_definition_type_hook, models.LexPrimitive: _lex_primitive_type_hook} _DEFAULT_DACITE_CONFIG = dacite.Config(cast=[Enum], type_hooks=_TYPE_HOOKS, check_types=_DEBUG) +L = t.TypeVar('L') -def lexicon_parse(data: dict, data_class=None): + +def lexicon_parse(data: dict, data_class: t.Optional[t.Type[L]] = None) -> t.Union[L, models.LexiconDoc]: if not data_class: data_class = models.LexiconDoc @@ -77,7 +79,7 @@ def lexicon_parse_file(lexicon_path: t.Union[Path, str], *, soft_fail: bool = Fa with open(lexicon_path, 'r', encoding='UTF-8') as f: plain_lexicon = json.loads(f.read()) return lexicon_parse(plain_lexicon) - except Exception as e: + except Exception as e: # noqa: BLE001 if soft_fail: return None @@ -101,9 +103,3 @@ def lexicon_parse_dir(path: t.Union[Path, str] = None, *, soft_fail: bool = Fals parsed_lexicons.append(parsed_lexicon) return parsed_lexicons - - -if __name__ == '__main__': - lex = lexicon_parse_file(_PATH_TO_LEXICONS.joinpath('app.bsky.actor.profile.json')) - all_lex = lexicon_parse_dir(_PATH_TO_LEXICONS) - print('Done') diff --git a/atproto/nsid/__init__.py b/atproto/nsid/__init__.py index fd2e776c..816d50fe 100644 --- a/atproto/nsid/__init__.py +++ b/atproto/nsid/__init__.py @@ -60,13 +60,13 @@ def name(self) -> str: """Get name.""" return self.segments[-1] - def __str__(self): + def __str__(self) -> str: return _NSID_DELIM.join(self.segments) - def __hash__(self): + def __hash__(self) -> int: return hash(str(self)) - def __eq__(self, other): + def __eq__(self, other) -> bool: if isinstance(other, NSID): return hash(self) == hash(other) @@ -176,17 +176,3 @@ def validate_nsid(nsid: str, *, soft_fail: bool = False) -> bool: return False raise e - - -if __name__ == '__main__': - assert validate_nsid('com.atproto.repo-.*', soft_fail=True) is False - assert validate_nsid('com.atproto', soft_fail=True) is False - assert validate_nsid('com.atproto' + '.test' * 90, soft_fail=True) is False - assert validate_nsid('com.atproto.repo.getRecord', soft_fail=True) is True - assert validate_nsid('com.atproto.1repo.getRecord', soft_fail=True) is False - assert validate_nsid('com.atproto.repo1.getRecord', soft_fail=True) is True - - nsid_obj = NSID.from_str('com.atproto.repo.getRecord') - assert nsid_obj.segments == ['com', 'atproto', 'repo', 'getRecord'] - assert nsid_obj.authority == 'repo.atproto.com' - assert nsid_obj.name == 'getRecord' diff --git a/atproto/uri/__init__.py b/atproto/uri/__init__.py index 8c47d3b4..b75ab915 100644 --- a/atproto/uri/__init__.py +++ b/atproto/uri/__init__.py @@ -24,7 +24,7 @@ class AtUri: Record Field: at://alice.host.com/io.example.song/3yI5-c1z-cc2p-1a#/title """ - def __init__(self, host: str, pathname: str, hash_: str, search_params: t.List[t.Tuple[str, t.Any]]): + def __init__(self, host: str, pathname: str, hash_: str, search_params: t.List[t.Tuple[str, t.Any]]) -> None: self.host = host self.pathname = pathname self.hash = hash_ @@ -64,7 +64,7 @@ def rkey(self) -> str: return '' @property - def http(self): + def http(self) -> str: """Convert instance to HTTP URI.""" return str(self) @@ -93,24 +93,11 @@ def __str__(self) -> str: return f'at://{self.host}{path}{query}{hash_}' - def __hash__(self): + def __hash__(self) -> int: return hash(str(self)) - def __eq__(self, other): + def __eq__(self, other) -> bool: if isinstance(other, AtUri): return hash(self) == hash(other) return False - - -if __name__ == '__main__': - test_uri = 'at://did:plc:poqvcn9iqfkgukdvqvb2qzba/app.bsky.feed.post/1jlmwihiomm9m' - - at_uri = AtUri.from_str(test_uri) - at_uri2 = AtUri.from_str(test_uri) - - assert at_uri == at_uri2 - - print(at_uri) - print(at_uri.rkey) - print(at_uri.collection) diff --git a/atproto/xrpc_client/client/async_client.py b/atproto/xrpc_client/client/async_client.py index 405b80fa..7c4fb285 100644 --- a/atproto/xrpc_client/client/async_client.py +++ b/atproto/xrpc_client/client/async_client.py @@ -21,7 +21,7 @@ class AsyncClient(AsyncClientRaw, SessionMethodsMixin): """High-level client for XRPC of ATProto.""" - def __init__(self, base_url: str = None): + def __init__(self, base_url: str = None) -> None: super().__init__(base_url) self._access_jwt: t.Optional[str] = None diff --git a/atproto/xrpc_client/client/async_raw.py b/atproto/xrpc_client/client/async_raw.py index c6e24a8e..245a80ba 100644 --- a/atproto/xrpc_client/client/async_raw.py +++ b/atproto/xrpc_client/client/async_raw.py @@ -10,7 +10,7 @@ class AsyncClientRaw(AsyncClientBase): com: 'async_ns.ComNamespace' bsky: 'async_ns.BskyNamespace' - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.com = async_ns.ComNamespace(self) diff --git a/atproto/xrpc_client/client/base.py b/atproto/xrpc_client/client/base.py index d4284d57..25b5ea7f 100644 --- a/atproto/xrpc_client/client/base.py +++ b/atproto/xrpc_client/client/base.py @@ -44,7 +44,7 @@ def _handle_kwagrs(kwargs: dict) -> None: class ClientBase: """Low-level methods are here""" - def __init__(self, base_url: t.Optional[str] = None, request: t.Optional[Request] = None): + def __init__(self, base_url: t.Optional[str] = None, request: t.Optional[Request] = None) -> None: if request is None: request = Request() if base_url is None: @@ -75,14 +75,13 @@ def _invoke(self, invoke_type: InvokeType, **kwargs) -> Response: if invoke_type is InvokeType.QUERY: return self.request.get(**kwargs) - else: - return self.request.post(**kwargs) + return self.request.post(**kwargs) class AsyncClientBase: """Low-level methods are here""" - def __init__(self, base_url: t.Optional[str] = None, request: t.Optional[AsyncRequest] = None): + def __init__(self, base_url: t.Optional[str] = None, request: t.Optional[AsyncRequest] = None) -> None: if request is None: request = AsyncRequest() if base_url is None: @@ -113,5 +112,4 @@ async def _invoke(self, invoke_type: InvokeType, **kwargs) -> Response: if invoke_type is InvokeType.QUERY: return await self.request.get(**kwargs) - else: - return await self.request.post(**kwargs) + return await self.request.post(**kwargs) diff --git a/atproto/xrpc_client/client/client.py b/atproto/xrpc_client/client/client.py index 09264138..f0a00692 100644 --- a/atproto/xrpc_client/client/client.py +++ b/atproto/xrpc_client/client/client.py @@ -15,7 +15,7 @@ class Client(ClientRaw, SessionMethodsMixin): """High-level client for XRPC of ATProto.""" - def __init__(self, base_url: str = None): + def __init__(self, base_url: str = None) -> None: super().__init__(base_url) self._access_jwt: t.Optional[str] = None diff --git a/atproto/xrpc_client/client/methods_mixin/session.py b/atproto/xrpc_client/client/methods_mixin/session.py index 12d5708d..8e207076 100644 --- a/atproto/xrpc_client/client/methods_mixin/session.py +++ b/atproto/xrpc_client/client/methods_mixin/session.py @@ -35,4 +35,4 @@ def _get_auth_headers(token: str) -> t.Dict[str, str]: return {'Authorization': f'Bearer {token}'} def _set_auth_headers(self, token: str) -> None: - self.request.set_additional_headers(self._get_auth_headers(token)) # noqa + self.request.set_additional_headers(self._get_auth_headers(token)) diff --git a/atproto/xrpc_client/client/raw.py b/atproto/xrpc_client/client/raw.py index cead92a8..fee6096f 100644 --- a/atproto/xrpc_client/client/raw.py +++ b/atproto/xrpc_client/client/raw.py @@ -10,7 +10,7 @@ class ClientRaw(ClientBase): com: 'sync_ns.ComNamespace' bsky: 'sync_ns.BskyNamespace' - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.com = sync_ns.ComNamespace(self) diff --git a/atproto/xrpc_client/models/__init__.py b/atproto/xrpc_client/models/__init__.py index 538a945a..b34d5242 100644 --- a/atproto/xrpc_client/models/__init__.py +++ b/atproto/xrpc_client/models/__init__.py @@ -1,295 +1,123 @@ from atproto.xrpc_client.models.app.bsky.actor import defs as AppBskyActorDefs -from atproto.xrpc_client.models.app.bsky.actor import ( - get_preferences as AppBskyActorGetPreferences, -) -from atproto.xrpc_client.models.app.bsky.actor import ( - get_profile as AppBskyActorGetProfile, -) -from atproto.xrpc_client.models.app.bsky.actor import ( - get_profiles as AppBskyActorGetProfiles, -) -from atproto.xrpc_client.models.app.bsky.actor import ( - get_suggestions as AppBskyActorGetSuggestions, -) +from atproto.xrpc_client.models.app.bsky.actor import get_preferences as AppBskyActorGetPreferences +from atproto.xrpc_client.models.app.bsky.actor import get_profile as AppBskyActorGetProfile +from atproto.xrpc_client.models.app.bsky.actor import get_profiles as AppBskyActorGetProfiles +from atproto.xrpc_client.models.app.bsky.actor import get_suggestions as AppBskyActorGetSuggestions from atproto.xrpc_client.models.app.bsky.actor import profile as AppBskyActorProfile -from atproto.xrpc_client.models.app.bsky.actor import ( - put_preferences as AppBskyActorPutPreferences, -) -from atproto.xrpc_client.models.app.bsky.actor import ( - search_actors as AppBskyActorSearchActors, -) -from atproto.xrpc_client.models.app.bsky.actor import ( - search_actors_typeahead as AppBskyActorSearchActorsTypeahead, -) +from atproto.xrpc_client.models.app.bsky.actor import put_preferences as AppBskyActorPutPreferences +from atproto.xrpc_client.models.app.bsky.actor import search_actors as AppBskyActorSearchActors +from atproto.xrpc_client.models.app.bsky.actor import search_actors_typeahead as AppBskyActorSearchActorsTypeahead from atproto.xrpc_client.models.app.bsky.embed import external as AppBskyEmbedExternal from atproto.xrpc_client.models.app.bsky.embed import images as AppBskyEmbedImages from atproto.xrpc_client.models.app.bsky.embed import record as AppBskyEmbedRecord -from atproto.xrpc_client.models.app.bsky.embed import ( - record_with_media as AppBskyEmbedRecordWithMedia, -) +from atproto.xrpc_client.models.app.bsky.embed import record_with_media as AppBskyEmbedRecordWithMedia from atproto.xrpc_client.models.app.bsky.feed import defs as AppBskyFeedDefs -from atproto.xrpc_client.models.app.bsky.feed import ( - describe_feed_generator as AppBskyFeedDescribeFeedGenerator, -) +from atproto.xrpc_client.models.app.bsky.feed import describe_feed_generator as AppBskyFeedDescribeFeedGenerator from atproto.xrpc_client.models.app.bsky.feed import generator as AppBskyFeedGenerator -from atproto.xrpc_client.models.app.bsky.feed import ( - get_actor_feeds as AppBskyFeedGetActorFeeds, -) -from atproto.xrpc_client.models.app.bsky.feed import ( - get_author_feed as AppBskyFeedGetAuthorFeed, -) +from atproto.xrpc_client.models.app.bsky.feed import get_actor_feeds as AppBskyFeedGetActorFeeds +from atproto.xrpc_client.models.app.bsky.feed import get_author_feed as AppBskyFeedGetAuthorFeed from atproto.xrpc_client.models.app.bsky.feed import get_feed as AppBskyFeedGetFeed -from atproto.xrpc_client.models.app.bsky.feed import ( - get_feed_generator as AppBskyFeedGetFeedGenerator, -) -from atproto.xrpc_client.models.app.bsky.feed import ( - get_feed_generators as AppBskyFeedGetFeedGenerators, -) -from atproto.xrpc_client.models.app.bsky.feed import ( - get_feed_skeleton as AppBskyFeedGetFeedSkeleton, -) +from atproto.xrpc_client.models.app.bsky.feed import get_feed_generator as AppBskyFeedGetFeedGenerator +from atproto.xrpc_client.models.app.bsky.feed import get_feed_generators as AppBskyFeedGetFeedGenerators +from atproto.xrpc_client.models.app.bsky.feed import get_feed_skeleton as AppBskyFeedGetFeedSkeleton from atproto.xrpc_client.models.app.bsky.feed import get_likes as AppBskyFeedGetLikes -from atproto.xrpc_client.models.app.bsky.feed import ( - get_post_thread as AppBskyFeedGetPostThread, -) +from atproto.xrpc_client.models.app.bsky.feed import get_post_thread as AppBskyFeedGetPostThread from atproto.xrpc_client.models.app.bsky.feed import get_posts as AppBskyFeedGetPosts -from atproto.xrpc_client.models.app.bsky.feed import ( - get_reposted_by as AppBskyFeedGetRepostedBy, -) -from atproto.xrpc_client.models.app.bsky.feed import ( - get_timeline as AppBskyFeedGetTimeline, -) +from atproto.xrpc_client.models.app.bsky.feed import get_reposted_by as AppBskyFeedGetRepostedBy +from atproto.xrpc_client.models.app.bsky.feed import get_timeline as AppBskyFeedGetTimeline from atproto.xrpc_client.models.app.bsky.feed import like as AppBskyFeedLike from atproto.xrpc_client.models.app.bsky.feed import post as AppBskyFeedPost from atproto.xrpc_client.models.app.bsky.feed import repost as AppBskyFeedRepost from atproto.xrpc_client.models.app.bsky.graph import block as AppBskyGraphBlock from atproto.xrpc_client.models.app.bsky.graph import defs as AppBskyGraphDefs from atproto.xrpc_client.models.app.bsky.graph import follow as AppBskyGraphFollow -from atproto.xrpc_client.models.app.bsky.graph import ( - get_blocks as AppBskyGraphGetBlocks, -) -from atproto.xrpc_client.models.app.bsky.graph import ( - get_followers as AppBskyGraphGetFollowers, -) -from atproto.xrpc_client.models.app.bsky.graph import ( - get_follows as AppBskyGraphGetFollows, -) +from atproto.xrpc_client.models.app.bsky.graph import get_blocks as AppBskyGraphGetBlocks +from atproto.xrpc_client.models.app.bsky.graph import get_followers as AppBskyGraphGetFollowers +from atproto.xrpc_client.models.app.bsky.graph import get_follows as AppBskyGraphGetFollows from atproto.xrpc_client.models.app.bsky.graph import get_list as AppBskyGraphGetList -from atproto.xrpc_client.models.app.bsky.graph import ( - get_list_mutes as AppBskyGraphGetListMutes, -) +from atproto.xrpc_client.models.app.bsky.graph import get_list_mutes as AppBskyGraphGetListMutes from atproto.xrpc_client.models.app.bsky.graph import get_lists as AppBskyGraphGetLists from atproto.xrpc_client.models.app.bsky.graph import get_mutes as AppBskyGraphGetMutes from atproto.xrpc_client.models.app.bsky.graph import list as AppBskyGraphList from atproto.xrpc_client.models.app.bsky.graph import listitem as AppBskyGraphListitem -from atproto.xrpc_client.models.app.bsky.graph import ( - mute_actor as AppBskyGraphMuteActor, -) -from atproto.xrpc_client.models.app.bsky.graph import ( - mute_actor_list as AppBskyGraphMuteActorList, -) -from atproto.xrpc_client.models.app.bsky.graph import ( - unmute_actor as AppBskyGraphUnmuteActor, -) -from atproto.xrpc_client.models.app.bsky.graph import ( - unmute_actor_list as AppBskyGraphUnmuteActorList, -) -from atproto.xrpc_client.models.app.bsky.notification import ( - get_unread_count as AppBskyNotificationGetUnreadCount, -) -from atproto.xrpc_client.models.app.bsky.notification import ( - list_notifications as AppBskyNotificationListNotifications, -) -from atproto.xrpc_client.models.app.bsky.notification import ( - update_seen as AppBskyNotificationUpdateSeen, -) +from atproto.xrpc_client.models.app.bsky.graph import mute_actor as AppBskyGraphMuteActor +from atproto.xrpc_client.models.app.bsky.graph import mute_actor_list as AppBskyGraphMuteActorList +from atproto.xrpc_client.models.app.bsky.graph import unmute_actor as AppBskyGraphUnmuteActor +from atproto.xrpc_client.models.app.bsky.graph import unmute_actor_list as AppBskyGraphUnmuteActorList +from atproto.xrpc_client.models.app.bsky.notification import get_unread_count as AppBskyNotificationGetUnreadCount +from atproto.xrpc_client.models.app.bsky.notification import list_notifications as AppBskyNotificationListNotifications +from atproto.xrpc_client.models.app.bsky.notification import update_seen as AppBskyNotificationUpdateSeen from atproto.xrpc_client.models.app.bsky.richtext import facet as AppBskyRichtextFacet -from atproto.xrpc_client.models.app.bsky.unspecced import ( - get_popular as AppBskyUnspeccedGetPopular, -) +from atproto.xrpc_client.models.app.bsky.unspecced import get_popular as AppBskyUnspeccedGetPopular from atproto.xrpc_client.models.app.bsky.unspecced import ( get_popular_feed_generators as AppBskyUnspeccedGetPopularFeedGenerators, ) from atproto.xrpc_client.models.com.atproto.admin import defs as ComAtprotoAdminDefs -from atproto.xrpc_client.models.com.atproto.admin import ( - disable_account_invites as ComAtprotoAdminDisableAccountInvites, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - disable_invite_codes as ComAtprotoAdminDisableInviteCodes, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - enable_account_invites as ComAtprotoAdminEnableAccountInvites, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_invite_codes as ComAtprotoAdminGetInviteCodes, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_moderation_action as ComAtprotoAdminGetModerationAction, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_moderation_actions as ComAtprotoAdminGetModerationActions, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_moderation_report as ComAtprotoAdminGetModerationReport, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_moderation_reports as ComAtprotoAdminGetModerationReports, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_record as ComAtprotoAdminGetRecord, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - get_repo as ComAtprotoAdminGetRepo, -) +from atproto.xrpc_client.models.com.atproto.admin import disable_account_invites as ComAtprotoAdminDisableAccountInvites +from atproto.xrpc_client.models.com.atproto.admin import disable_invite_codes as ComAtprotoAdminDisableInviteCodes +from atproto.xrpc_client.models.com.atproto.admin import enable_account_invites as ComAtprotoAdminEnableAccountInvites +from atproto.xrpc_client.models.com.atproto.admin import get_invite_codes as ComAtprotoAdminGetInviteCodes +from atproto.xrpc_client.models.com.atproto.admin import get_moderation_action as ComAtprotoAdminGetModerationAction +from atproto.xrpc_client.models.com.atproto.admin import get_moderation_actions as ComAtprotoAdminGetModerationActions +from atproto.xrpc_client.models.com.atproto.admin import get_moderation_report as ComAtprotoAdminGetModerationReport +from atproto.xrpc_client.models.com.atproto.admin import get_moderation_reports as ComAtprotoAdminGetModerationReports +from atproto.xrpc_client.models.com.atproto.admin import get_record as ComAtprotoAdminGetRecord +from atproto.xrpc_client.models.com.atproto.admin import get_repo as ComAtprotoAdminGetRepo from atproto.xrpc_client.models.com.atproto.admin import ( resolve_moderation_reports as ComAtprotoAdminResolveModerationReports, ) from atproto.xrpc_client.models.com.atproto.admin import ( reverse_moderation_action as ComAtprotoAdminReverseModerationAction, ) -from atproto.xrpc_client.models.com.atproto.admin import ( - search_repos as ComAtprotoAdminSearchRepos, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - take_moderation_action as ComAtprotoAdminTakeModerationAction, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - update_account_email as ComAtprotoAdminUpdateAccountEmail, -) -from atproto.xrpc_client.models.com.atproto.admin import ( - update_account_handle as ComAtprotoAdminUpdateAccountHandle, -) -from atproto.xrpc_client.models.com.atproto.identity import ( - resolve_handle as ComAtprotoIdentityResolveHandle, -) -from atproto.xrpc_client.models.com.atproto.identity import ( - update_handle as ComAtprotoIdentityUpdateHandle, -) +from atproto.xrpc_client.models.com.atproto.admin import search_repos as ComAtprotoAdminSearchRepos +from atproto.xrpc_client.models.com.atproto.admin import take_moderation_action as ComAtprotoAdminTakeModerationAction +from atproto.xrpc_client.models.com.atproto.admin import update_account_email as ComAtprotoAdminUpdateAccountEmail +from atproto.xrpc_client.models.com.atproto.admin import update_account_handle as ComAtprotoAdminUpdateAccountHandle +from atproto.xrpc_client.models.com.atproto.identity import resolve_handle as ComAtprotoIdentityResolveHandle +from atproto.xrpc_client.models.com.atproto.identity import update_handle as ComAtprotoIdentityUpdateHandle from atproto.xrpc_client.models.com.atproto.label import defs as ComAtprotoLabelDefs -from atproto.xrpc_client.models.com.atproto.label import ( - query_labels as ComAtprotoLabelQueryLabels, -) -from atproto.xrpc_client.models.com.atproto.label import ( - subscribe_labels as ComAtprotoLabelSubscribeLabels, -) -from atproto.xrpc_client.models.com.atproto.moderation import ( - create_report as ComAtprotoModerationCreateReport, -) -from atproto.xrpc_client.models.com.atproto.moderation import ( - defs as ComAtprotoModerationDefs, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - apply_writes as ComAtprotoRepoApplyWrites, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - create_record as ComAtprotoRepoCreateRecord, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - delete_record as ComAtprotoRepoDeleteRecord, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - describe_repo as ComAtprotoRepoDescribeRepo, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - get_record as ComAtprotoRepoGetRecord, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - list_records as ComAtprotoRepoListRecords, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - put_record as ComAtprotoRepoPutRecord, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - rebase_repo as ComAtprotoRepoRebaseRepo, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - strong_ref as ComAtprotoRepoStrongRef, -) -from atproto.xrpc_client.models.com.atproto.repo import ( - upload_blob as ComAtprotoRepoUploadBlob, -) -from atproto.xrpc_client.models.com.atproto.server import ( - create_account as ComAtprotoServerCreateAccount, -) -from atproto.xrpc_client.models.com.atproto.server import ( - create_app_password as ComAtprotoServerCreateAppPassword, -) -from atproto.xrpc_client.models.com.atproto.server import ( - create_invite_code as ComAtprotoServerCreateInviteCode, -) -from atproto.xrpc_client.models.com.atproto.server import ( - create_invite_codes as ComAtprotoServerCreateInviteCodes, -) -from atproto.xrpc_client.models.com.atproto.server import ( - create_session as ComAtprotoServerCreateSession, -) +from atproto.xrpc_client.models.com.atproto.label import query_labels as ComAtprotoLabelQueryLabels +from atproto.xrpc_client.models.com.atproto.label import subscribe_labels as ComAtprotoLabelSubscribeLabels +from atproto.xrpc_client.models.com.atproto.moderation import create_report as ComAtprotoModerationCreateReport +from atproto.xrpc_client.models.com.atproto.moderation import defs as ComAtprotoModerationDefs +from atproto.xrpc_client.models.com.atproto.repo import apply_writes as ComAtprotoRepoApplyWrites +from atproto.xrpc_client.models.com.atproto.repo import create_record as ComAtprotoRepoCreateRecord +from atproto.xrpc_client.models.com.atproto.repo import delete_record as ComAtprotoRepoDeleteRecord +from atproto.xrpc_client.models.com.atproto.repo import describe_repo as ComAtprotoRepoDescribeRepo +from atproto.xrpc_client.models.com.atproto.repo import get_record as ComAtprotoRepoGetRecord +from atproto.xrpc_client.models.com.atproto.repo import list_records as ComAtprotoRepoListRecords +from atproto.xrpc_client.models.com.atproto.repo import put_record as ComAtprotoRepoPutRecord +from atproto.xrpc_client.models.com.atproto.repo import rebase_repo as ComAtprotoRepoRebaseRepo +from atproto.xrpc_client.models.com.atproto.repo import strong_ref as ComAtprotoRepoStrongRef +from atproto.xrpc_client.models.com.atproto.repo import upload_blob as ComAtprotoRepoUploadBlob +from atproto.xrpc_client.models.com.atproto.server import create_account as ComAtprotoServerCreateAccount +from atproto.xrpc_client.models.com.atproto.server import create_app_password as ComAtprotoServerCreateAppPassword +from atproto.xrpc_client.models.com.atproto.server import create_invite_code as ComAtprotoServerCreateInviteCode +from atproto.xrpc_client.models.com.atproto.server import create_invite_codes as ComAtprotoServerCreateInviteCodes +from atproto.xrpc_client.models.com.atproto.server import create_session as ComAtprotoServerCreateSession from atproto.xrpc_client.models.com.atproto.server import defs as ComAtprotoServerDefs -from atproto.xrpc_client.models.com.atproto.server import ( - delete_account as ComAtprotoServerDeleteAccount, -) -from atproto.xrpc_client.models.com.atproto.server import ( - delete_session as ComAtprotoServerDeleteSession, -) -from atproto.xrpc_client.models.com.atproto.server import ( - describe_server as ComAtprotoServerDescribeServer, -) +from atproto.xrpc_client.models.com.atproto.server import delete_account as ComAtprotoServerDeleteAccount +from atproto.xrpc_client.models.com.atproto.server import delete_session as ComAtprotoServerDeleteSession +from atproto.xrpc_client.models.com.atproto.server import describe_server as ComAtprotoServerDescribeServer from atproto.xrpc_client.models.com.atproto.server import ( get_account_invite_codes as ComAtprotoServerGetAccountInviteCodes, ) -from atproto.xrpc_client.models.com.atproto.server import ( - get_session as ComAtprotoServerGetSession, -) -from atproto.xrpc_client.models.com.atproto.server import ( - list_app_passwords as ComAtprotoServerListAppPasswords, -) -from atproto.xrpc_client.models.com.atproto.server import ( - refresh_session as ComAtprotoServerRefreshSession, -) -from atproto.xrpc_client.models.com.atproto.server import ( - request_account_delete as ComAtprotoServerRequestAccountDelete, -) -from atproto.xrpc_client.models.com.atproto.server import ( - request_password_reset as ComAtprotoServerRequestPasswordReset, -) -from atproto.xrpc_client.models.com.atproto.server import ( - reset_password as ComAtprotoServerResetPassword, -) -from atproto.xrpc_client.models.com.atproto.server import ( - revoke_app_password as ComAtprotoServerRevokeAppPassword, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_blob as ComAtprotoSyncGetBlob, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_blocks as ComAtprotoSyncGetBlocks, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_checkout as ComAtprotoSyncGetCheckout, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_commit_path as ComAtprotoSyncGetCommitPath, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_head as ComAtprotoSyncGetHead, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_record as ComAtprotoSyncGetRecord, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - get_repo as ComAtprotoSyncGetRepo, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - list_blobs as ComAtprotoSyncListBlobs, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - list_repos as ComAtprotoSyncListRepos, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - notify_of_update as ComAtprotoSyncNotifyOfUpdate, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - request_crawl as ComAtprotoSyncRequestCrawl, -) -from atproto.xrpc_client.models.com.atproto.sync import ( - subscribe_repos as ComAtprotoSyncSubscribeRepos, -) +from atproto.xrpc_client.models.com.atproto.server import get_session as ComAtprotoServerGetSession +from atproto.xrpc_client.models.com.atproto.server import list_app_passwords as ComAtprotoServerListAppPasswords +from atproto.xrpc_client.models.com.atproto.server import refresh_session as ComAtprotoServerRefreshSession +from atproto.xrpc_client.models.com.atproto.server import request_account_delete as ComAtprotoServerRequestAccountDelete +from atproto.xrpc_client.models.com.atproto.server import request_password_reset as ComAtprotoServerRequestPasswordReset +from atproto.xrpc_client.models.com.atproto.server import reset_password as ComAtprotoServerResetPassword +from atproto.xrpc_client.models.com.atproto.server import revoke_app_password as ComAtprotoServerRevokeAppPassword +from atproto.xrpc_client.models.com.atproto.sync import get_blob as ComAtprotoSyncGetBlob +from atproto.xrpc_client.models.com.atproto.sync import get_blocks as ComAtprotoSyncGetBlocks +from atproto.xrpc_client.models.com.atproto.sync import get_checkout as ComAtprotoSyncGetCheckout +from atproto.xrpc_client.models.com.atproto.sync import get_commit_path as ComAtprotoSyncGetCommitPath +from atproto.xrpc_client.models.com.atproto.sync import get_head as ComAtprotoSyncGetHead +from atproto.xrpc_client.models.com.atproto.sync import get_record as ComAtprotoSyncGetRecord +from atproto.xrpc_client.models.com.atproto.sync import get_repo as ComAtprotoSyncGetRepo +from atproto.xrpc_client.models.com.atproto.sync import list_blobs as ComAtprotoSyncListBlobs +from atproto.xrpc_client.models.com.atproto.sync import list_repos as ComAtprotoSyncListRepos +from atproto.xrpc_client.models.com.atproto.sync import notify_of_update as ComAtprotoSyncNotifyOfUpdate +from atproto.xrpc_client.models.com.atproto.sync import request_crawl as ComAtprotoSyncRequestCrawl +from atproto.xrpc_client.models.com.atproto.sync import subscribe_repos as ComAtprotoSyncSubscribeRepos diff --git a/atproto/xrpc_client/models/base.py b/atproto/xrpc_client/models/base.py index 51c639bf..e7a0ae7c 100644 --- a/atproto/xrpc_client/models/base.py +++ b/atproto/xrpc_client/models/base.py @@ -5,7 +5,7 @@ @dataclass class ModelBase: - def __getitem__(self, item): + def __getitem__(self, item: str): if hasattr(self, item): return getattr(self, item) diff --git a/atproto/xrpc_client/models/blob_ref.py b/atproto/xrpc_client/models/blob_ref.py index 11992a7c..852929c5 100644 --- a/atproto/xrpc_client/models/blob_ref.py +++ b/atproto/xrpc_client/models/blob_ref.py @@ -2,7 +2,7 @@ class BlobRef: - def __init__(self, blob_type: str, mime_type: str, ref: str, size: int): + def __init__(self, blob_type: str, mime_type: str, ref: str, size: int) -> None: self.blob_type = blob_type self.mime_type = mime_type self.ref = ref diff --git a/atproto/xrpc_client/models/utils.py b/atproto/xrpc_client/models/utils.py index 30ffc0f9..264f8b67 100644 --- a/atproto/xrpc_client/models/utils.py +++ b/atproto/xrpc_client/models/utils.py @@ -13,7 +13,7 @@ UnexpectedFieldError, WrongTypeError, ) -from atproto.xrpc_client.models.base import RecordModelBase +from atproto.xrpc_client.models.base import ModelBase, RecordModelBase from atproto.xrpc_client.models.blob_ref import BlobRef from atproto.xrpc_client.models.type_conversion import RECORD_TYPE_TO_MODEL_CLASS @@ -58,15 +58,15 @@ def get_or_create_model(model_data: t.Union[dict], model: t.Type[M]) -> t.Option except TypeError as e: # FIXME(MarshalX): "Params missing 1 required positional argument: 'rkey'" should raise another error msg = str(e).replace('__init__()', model.__name__) - raise UnexpectedFieldError(msg) + raise UnexpectedFieldError(msg) from e except exceptions.MissingValueError as e: - raise MissingValueError(str(e)) + raise MissingValueError(str(e)) from e except exceptions.WrongTypeError as e: - raise WrongTypeError(str(e)) + raise WrongTypeError(str(e)) from e except exceptions.DaciteFieldError as e: - raise ModelFieldError(str(e)) + raise ModelFieldError(str(e)) from e except exceptions.DaciteError as e: - raise ModelError(str(e)) + raise ModelError(str(e)) from e def get_response_model(response: 'Response', model: t.Type[M]) -> t.Optional[M]: @@ -87,18 +87,18 @@ def _handle_dict_key(key: str) -> str: def _handle_dict_value(ref: t.Any) -> t.Any: if isinstance(ref, BlobRef): return ref.to_dict() - elif isinstance(ref, CID): + if isinstance(ref, CID): return ref.encode() return ref -def _model_as_dict_factory(value): +def _model_as_dict_factory(value) -> dict: # exclude None values and process keys and values return {_handle_dict_key(k): _handle_dict_value(v) for k, v in value if v is not None} -def get_model_as_dict(model) -> dict: +def get_model_as_dict(model: t.Union[BlobRef, ModelBase]) -> dict: if model == BlobRef: return model.to_dict() @@ -108,16 +108,16 @@ def get_model_as_dict(model) -> dict: return dataclasses.asdict(model, dict_factory=_model_as_dict_factory) -def get_model_as_json(model) -> str: +def get_model_as_json(model: t.Union[BlobRef, ModelBase]) -> str: return json.dumps(get_model_as_dict(model)) -def is_json(json_data: t.Union[str, bytes]): +def is_json(json_data: t.Union[str, bytes]) -> bool: if isinstance(json_data, bytes): json_data.decode('UTF-8') try: json.loads(json_data) return True - except Exception: # noqa + except: # noqa return False diff --git a/atproto/xrpc_client/namespaces/async_ns.py b/atproto/xrpc_client/namespaces/async_ns.py index b24f1b8f..7294f080 100644 --- a/atproto/xrpc_client/namespaces/async_ns.py +++ b/atproto/xrpc_client/namespaces/async_ns.py @@ -17,7 +17,7 @@ class AppNamespace(NamespaceBase): bsky: 'BskyNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.bsky = BskyNamespace(self._client) @@ -29,7 +29,7 @@ class BskyNamespace(NamespaceBase): notification: 'NotificationNamespace' = field(default_factory=DefaultNamespace) unspecced: 'UnspeccedNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.actor = ActorNamespace(self._client) self.feed = FeedNamespace(self._client) self.graph = GraphNamespace(self._client) @@ -806,7 +806,7 @@ async def update_seen(self, data: t.Union[dict, 'models.AppBskyNotificationUpdat class ComNamespace(NamespaceBase): atproto: 'AtprotoNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.atproto = AtprotoNamespace(self._client) @@ -820,7 +820,7 @@ class AtprotoNamespace(NamespaceBase): server: 'ServerNamespace' = field(default_factory=DefaultNamespace) sync: 'SyncNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.admin = AdminNamespace(self._client) self.identity = IdentityNamespace(self._client) self.label = LabelNamespace(self._client) diff --git a/atproto/xrpc_client/namespaces/sync_ns.py b/atproto/xrpc_client/namespaces/sync_ns.py index 29b250a0..feab46de 100644 --- a/atproto/xrpc_client/namespaces/sync_ns.py +++ b/atproto/xrpc_client/namespaces/sync_ns.py @@ -17,7 +17,7 @@ class AppNamespace(NamespaceBase): bsky: 'BskyNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.bsky = BskyNamespace(self._client) @@ -29,7 +29,7 @@ class BskyNamespace(NamespaceBase): notification: 'NotificationNamespace' = field(default_factory=DefaultNamespace) unspecced: 'UnspeccedNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.actor = ActorNamespace(self._client) self.feed = FeedNamespace(self._client) self.graph = GraphNamespace(self._client) @@ -806,7 +806,7 @@ def update_seen(self, data: t.Union[dict, 'models.AppBskyNotificationUpdateSeen. class ComNamespace(NamespaceBase): atproto: 'AtprotoNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.atproto = AtprotoNamespace(self._client) @@ -820,7 +820,7 @@ class AtprotoNamespace(NamespaceBase): server: 'ServerNamespace' = field(default_factory=DefaultNamespace) sync: 'SyncNamespace' = field(default_factory=DefaultNamespace) - def __post_init__(self): + def __post_init__(self) -> None: self.admin = AdminNamespace(self._client) self.identity = IdentityNamespace(self._client) self.label = LabelNamespace(self._client) diff --git a/atproto/xrpc_client/request.py b/atproto/xrpc_client/request.py index 88122642..4edbf55b 100644 --- a/atproto/xrpc_client/request.py +++ b/atproto/xrpc_client/request.py @@ -33,10 +33,10 @@ def _parse_response(response: httpx.Response) -> Response: def _handle_request_errors(exception: Exception) -> None: try: raise exception - except httpx.TimeoutException: - raise exceptions.InvokeTimeoutError() - except httpx.NetworkError: - raise exceptions.NetworkError() + except httpx.TimeoutException as e: + raise exceptions.InvokeTimeoutError from e + except httpx.NetworkError as e: + raise exceptions.NetworkError from e # TODO(MarshalX): add more exceptions @@ -56,18 +56,18 @@ def _handle_response(response: httpx.Response) -> httpx.Response: if response.status_code in {401, 403}: raise exceptions.UnauthorizedError(error_response) - elif response.status_code == 400: + if response.status_code == 400: raise exceptions.BadRequestError(error_response) - elif response.status_code in {409, 413, 502}: + if response.status_code in {409, 413, 502}: raise exceptions.NetworkError(error_response) - else: - raise exceptions.RequestException(error_response) + + raise exceptions.RequestException(error_response) class RequestBase: - MANDATORY_HEADERS = {'User-Agent': f'atproto/alpha (Python SDK)'} + MANDATORY_HEADERS = {'User-Agent': 'atproto/alpha (Python SDK)'} - def __init__(self): + def __init__(self) -> None: self._additional_headers: dict = {} def get_headers(self, additional_headers: t.Optional[dict] = None) -> dict: @@ -88,20 +88,20 @@ def set_additional_headers(self, headers: dict) -> None: class Request(RequestBase): """Class for handling requests errors and working with httpx""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._client = httpx.Client() - def _send_request(self, method: str, url: str, *args, **kwargs) -> httpx.Response: + def _send_request(self, method: str, url: str, **kwargs) -> httpx.Response: headers = self.get_headers(kwargs.pop('headers')) try: - response = self._client.request(method=method, url=url, headers=headers, *args, **kwargs) + response = self._client.request(method=method, url=url, headers=headers, **kwargs) return _handle_response(response) - except Exception as e: + except Exception as e: # noqa: BLE001 _handle_request_errors(e) - def close(self): + def close(self) -> None: self._client.close() def get(self, *args, **kwargs) -> Response: @@ -114,20 +114,20 @@ def post(self, *args, **kwargs) -> Response: class AsyncRequest(RequestBase): """Class for handling requests errors and working with httpx""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._client = httpx.AsyncClient() - async def _send_request(self, method: str, url: str, *args, **kwargs) -> httpx.Response: + async def _send_request(self, method: str, url: str, **kwargs) -> httpx.Response: headers = self.get_headers(kwargs.pop('headers')) try: - response = await self._client.request(method=method, url=url, headers=headers, *args, **kwargs) + response = await self._client.request(method=method, url=url, headers=headers, **kwargs) return _handle_response(response) - except Exception as e: + except Exception as e: # noqa: BLE001 _handle_request_errors(e) - async def close(self): + async def close(self) -> None: await self._client.aclose() async def get(self, *args, **kwargs) -> Response: diff --git a/poetry.lock b/poetry.lock index 9e26d528..3383d516 100644 --- a/poetry.lock +++ b/poetry.lock @@ -318,6 +318,21 @@ files = [ {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, ] +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "fonttools" version = "4.38.0" @@ -488,23 +503,17 @@ perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] -name = "isort" -version = "5.11.5" -description = "A Python utility / library to sort Python imports." +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" files = [ - {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, - {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - [[package]] name = "jinja2" version = "3.1.2" @@ -1018,6 +1027,25 @@ typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""} docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pygments" version = "2.15.1" @@ -1069,6 +1097,30 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pytest" +version = "7.3.1" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, + {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1168,6 +1220,33 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.0.270" +description = "An extremely fast Python linter, written in Rust." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.270-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f74c4d550f7b8e808455ac77bbce38daafc458434815ba0bc21ae4bdb276509b"}, + {file = "ruff-0.0.270-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:643de865fd35cb76c4f0739aea5afe7b8e4d40d623df7e9e6ea99054e5cead0a"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca02e709b3308eb7255b5f74e779be23b5980fca3862eae28bb23069cd61ae4"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ed3b198768d2b3a2300fb18f730cd39948a5cc36ba29ae9d4639a11040880be"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:739495d2dbde87cf4e3110c8d27bc20febf93112539a968a4e02c26f0deccd1d"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:08188f8351f4c0b6216e8463df0a76eb57894ca59a3da65e4ed205db980fd3ae"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0827b074635d37984fc98d99316bfab5c8b1231bb83e60dacc83bd92883eedb4"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d61ae4841313f6eeb8292dc349bef27b4ce426e62c36e80ceedc3824e408734"}, + {file = "ruff-0.0.270-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eb412f20e77529a01fb94d578b19dcb8331b56f93632aa0cce4a2ea27b7aeba"}, + {file = "ruff-0.0.270-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b775e2c5fc869359daf8c8b8aa0fd67240201ab2e8d536d14a0edf279af18786"}, + {file = "ruff-0.0.270-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:21f00e47ab2308617c44435c8dfd9e2e03897461c9e647ec942deb2a235b4cfd"}, + {file = "ruff-0.0.270-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0bbfbf6fd2436165566ca85f6e57be03ed2f0a994faf40180cfbb3604c9232ef"}, + {file = "ruff-0.0.270-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8af391ef81f7be960be10886a3c1aac0b298bde7cb9a86ec2b05faeb2081ce6b"}, + {file = "ruff-0.0.270-py3-none-win32.whl", hash = "sha256:b4c037fe2f75bcd9aed0c89c7c507cb7fa59abae2bd4c8b6fc331a28178655a4"}, + {file = "ruff-0.0.270-py3-none-win_amd64.whl", hash = "sha256:0012f9b7dc137ab7f1f0355e3c4ca49b562baf6c9fa1180948deeb6648c52957"}, + {file = "ruff-0.0.270-py3-none-win_arm64.whl", hash = "sha256:9613456b0b375766244c25045e353bc8890c856431cd97893c97b10cc93bd28d"}, + {file = "ruff-0.0.270.tar.gz", hash = "sha256:95db07b7850b30ebf32b27fe98bc39e0ab99db3985edbbf0754d399eb2f0e690"}, +] + [[package]] name = "six" version = "1.16.0" @@ -1548,4 +1627,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7,<3.12" -content-hash = "a8fb20a10309462673e6e8832f355c3d8f17cd18d2524b2d87dc850a742d78e0" +content-hash = "8c8ad8a8f1e331be0f9d94e9c0bdaf153d4c2219f9ae9b6cdf0ea14a74acd589" diff --git a/pyproject.toml b/pyproject.toml index 5d73391a..91dbb74c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,17 +46,19 @@ pyjwt = ">=2.7.0,<2.8.0" httpx-ws = ">=0.3.1,<0.4.0" click = ">=8.1.3,<8.2.0" -[tool.poetry.dev-dependencies] - [tool.poetry.group.dev.dependencies] black = "23.3.0" -isort = "5.11.5" sphinx = "5.3.0" myst-parser = "1.0.0" sphinx-copybutton = "0.5.2" sphinx-favicon = "1.0.1" furo = "2023.3.27" sphinxext-opengraph = "0.8.2" +ruff = ">=0.0.270,<0.1.0" + + +[tool.poetry.group.test.dependencies] +pytest = ">=7.3.1,<7.4.0" [tool.poetry-dynamic-versioning] # poetry self add "poetry-dynamic-versioning[plugin]" @@ -71,8 +73,56 @@ style = "pep440" requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" -[tool.isort] -profile = "black" +[tool.ruff] +extend-select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # Pyflakes + "I", # isort + "C90", # flake8-comprehensions + "B", # flake8-bugbear + "Q", # flake8-quotes + "S", # flake8-bandit + "ASYNC", # flake8-async +# "ANN", # flake8-annotations. Not all resolved yet + "C", + "BLE", + "ERA", + "ICN", + "INP", + "ISC", + "NPY", + "PGH", + "PIE", + "RET", + "RSE", + "RUF", + "SIM", + "T20", +# "TCH", # it can't be autofixed yet + "TID", + "YTT", +] +line-length = 120 +target-version = "py37" +ignore = [ + "E501", # line too long, handled by black + "PGH004", # use specific rule code with noqa; works bad with JetBrains IDE Warnings + "ANN002", # Missing type annotation for `*args` + "ANN003", # Missing type annotation for `**kwargs` + "ANN101", # Missing type annotation for `self` in method + "ANN102", # Missing type annotation for `cls` in classmethod +] + +[tool.ruff.per-file-ignores] +"atproto/xrpc_client/models/__init__.py" = ["F401"] +"test_*.py" = ["S101"] +"test.py" = ["S101", "ERA001", "T201", "E501", "F401"] + +[tool.ruff.flake8-quotes] +docstring-quotes = "double" +multiline-quotes = "double" +inline-quotes = "single" [tool.black] line-length = 120 diff --git a/test.py b/test.py index 149ef4f1..7ed67514 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,7 @@ import asyncio import logging import os +import typing as t import threading from atproto import CAR, AsyncClient, AtUri, Client, exceptions, models @@ -12,6 +13,9 @@ parse_subscribe_repos_message, ) +if t.TYPE_CHECKING: + from atproto.firehose import MessageFrame + # logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.INFO) @@ -40,11 +44,11 @@ def sync_main(): client.login(os.environ['USERNAME'], os.environ['PASSWORD']) # repo = client.com.atproto.sync.get_repo({'did': client.me.did}) - # did = client.com.atproto.identity.resolve_handle({'handle': 'bsky.app'}).did - # repo = client.com.atproto.sync.get_repo({'did': did}) - # car_file = CAR.from_bytes(repo) - # print(car_file.root) - # print(car_file.nodes) + did = client.com.atproto.identity.resolve_handle({'handle': 'bsky.app'}).did + repo = client.com.atproto.sync.get_repo({'did': did}) + car_file = CAR.from_bytes(repo) + print(car_file.root) + print(car_file.blocks) # res = client.com.atproto.repo.get_record(...) # implement by yourself # also you need to parse "res.value" as profile record using get_or_create_model method @@ -174,7 +178,7 @@ async def _stop_after_n_sec(): await asyncio.sleep(3) await client.stop() - asyncio.create_task(_stop_after_n_sec()) + _ = asyncio.create_task(_stop_after_n_sec()) await client.start(on_message_handler) # await client.start(on_message_handler, on_callback_error_handler) print('stopped. start again') @@ -184,8 +188,8 @@ async def _stop_after_n_sec(): if __name__ == '__main__': # test_strange_embed_images_type() - sync_main() + # sync_main() # asyncio.get_event_loop().run_until_complete(main()) - # _main_firehose_test() + _main_firehose_test() # asyncio.get_event_loop().run_until_complete(_main_async_firehose_test()) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_lexicon_parser.py b/tests/test_lexicon_parser.py new file mode 100644 index 00000000..2e68e04c --- /dev/null +++ b/tests/test_lexicon_parser.py @@ -0,0 +1,9 @@ +from atproto.lexicon.parser import _PATH_TO_LEXICONS, lexicon_parse_file, lexicon_parse_dir # noqa + + +def test_lexicon_parse_file(): + lexicon_parse_file(_PATH_TO_LEXICONS.joinpath('app.bsky.actor.profile.json')) + + +def test_lexicon_parse_dir(): + lexicon_parse_dir(_PATH_TO_LEXICONS) diff --git a/tests/test_nsid.py b/tests/test_nsid.py new file mode 100644 index 00000000..7ddac874 --- /dev/null +++ b/tests/test_nsid.py @@ -0,0 +1,17 @@ +from atproto.nsid import NSID, validate_nsid + + +def test_nsid_from_str(): + nsid_obj = NSID.from_str('com.atproto.repo.getRecord') + assert nsid_obj.segments == ['com', 'atproto', 'repo', 'getRecord'] + assert nsid_obj.authority == 'repo.atproto.com' + assert nsid_obj.name == 'getRecord' + + +def test_nsid_validation(): + assert validate_nsid('com.atproto.repo-.*', soft_fail=True) is False + assert validate_nsid('com.atproto', soft_fail=True) is False + assert validate_nsid('com.atproto' + '.test' * 90, soft_fail=True) is False + assert validate_nsid('com.atproto.repo.getRecord', soft_fail=True) is True + assert validate_nsid('com.atproto.1repo.getRecord', soft_fail=True) is False + assert validate_nsid('com.atproto.repo1.getRecord', soft_fail=True) is True diff --git a/tests/test_uri.py b/tests/test_uri.py new file mode 100644 index 00000000..20a639f9 --- /dev/null +++ b/tests/test_uri.py @@ -0,0 +1,14 @@ +from atproto import AtUri + + +def test_at_uri_from_str(): + test_uri = 'at://did:plc:poqvcn9iqfkgukdvqvb2qzba/app.bsky.feed.post/1jlmwihiomm9m' + + at_uri = AtUri.from_str(test_uri) + at_uri2 = AtUri.from_str(test_uri) + + assert at_uri == at_uri2 + + assert test_uri == str(at_uri) + assert at_uri.rkey == '1jlmwihiomm9m' + assert at_uri.collection == 'app.bsky.feed.post'