fragment-python 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. fragment/__init__.py +0 -0
  2. fragment/client/__init__.py +1 -0
  3. fragment/client/async_client.py +67 -0
  4. fragment/codegen/__init__.py +0 -0
  5. fragment/codegen/helpers.py +51 -0
  6. fragment/codegen/main.py +64 -0
  7. fragment/codegen/plugins/__init__.py +0 -0
  8. fragment/codegen/plugins/generate_client_method.py +62 -0
  9. fragment/codegen/plugins/get_file_comment.py +18 -0
  10. fragment/exceptions.py +12 -0
  11. fragment/logger.py +6 -0
  12. fragment/sdk/__init__.py +478 -0
  13. fragment/sdk/add_ledger_entry.py +54 -0
  14. fragment/sdk/add_ledger_entry_runtime.py +54 -0
  15. fragment/sdk/async_client.py +69 -0
  16. fragment/sdk/base_model.py +29 -0
  17. fragment/sdk/client.py +1071 -0
  18. fragment/sdk/create_custom_link.py +40 -0
  19. fragment/sdk/create_ledger.py +49 -0
  20. fragment/sdk/enums.py +263 -0
  21. fragment/sdk/get_ledger.py +23 -0
  22. fragment/sdk/get_ledger_account_balance.py +23 -0
  23. fragment/sdk/get_ledger_account_lines.py +47 -0
  24. fragment/sdk/get_ledger_entry.py +41 -0
  25. fragment/sdk/get_schema.py +28 -0
  26. fragment/sdk/get_workspace.py +16 -0
  27. fragment/sdk/input_types.py +482 -0
  28. fragment/sdk/list_ledger_account_balances.py +53 -0
  29. fragment/sdk/list_ledger_accounts.py +50 -0
  30. fragment/sdk/list_ledger_entries.py +58 -0
  31. fragment/sdk/list_ledger_entry_group_balances.py +57 -0
  32. fragment/sdk/list_multi_currency_ledger_account_balances.py +130 -0
  33. fragment/sdk/reconcile_tx.py +55 -0
  34. fragment/sdk/reconcile_tx_runtime.py +55 -0
  35. fragment/sdk/store_schema.py +45 -0
  36. fragment/sdk/sync_custom_accounts.py +53 -0
  37. fragment/sdk/sync_custom_txs.py +44 -0
  38. fragment/sdk/update_ledger.py +39 -0
  39. fragment/sdk/update_ledger_entry.py +77 -0
  40. fragment/std_queries/queries.graphql +656 -0
  41. fragment_python-0.1.1.dist-info/LICENSE +201 -0
  42. fragment_python-0.1.1.dist-info/METADATA +142 -0
  43. fragment_python-0.1.1.dist-info/RECORD +45 -0
  44. fragment_python-0.1.1.dist-info/WHEEL +4 -0
  45. fragment_python-0.1.1.dist-info/entry_points.txt +3 -0
fragment/__init__.py ADDED
File without changes
@@ -0,0 +1 @@
1
+ #!/usr/bin/env python3
@@ -0,0 +1,67 @@
1
+ # Ignore untyped authlib
2
+ # mypy: disable-error-code="import-untyped"
3
+ import time
4
+ from typing import Any, Dict, Optional
5
+
6
+ import httpx
7
+ from ariadne_codegen.client_generators.dependencies.async_base_client import (
8
+ AsyncBaseClient,
9
+ )
10
+ from authlib.integrations.httpx_client import AsyncOAuth2Client
11
+
12
+ from fragment.exceptions import MissingArgumentException, MissingTokenException
13
+
14
+
15
+ class AsyncFragmentClient(AsyncBaseClient):
16
+ def __init__(
17
+ self,
18
+ api_url: str = "",
19
+ auth_url: str = "",
20
+ auth_scope: str = "",
21
+ client_id: str = "",
22
+ client_secret: str = "",
23
+ http_client: Optional[httpx.AsyncClient] = None,
24
+ ):
25
+ if api_url == "":
26
+ raise MissingArgumentException("api_url")
27
+ if auth_url == "":
28
+ raise MissingArgumentException("auth_url")
29
+ if auth_scope == "":
30
+ raise MissingArgumentException("auth_scope")
31
+ if client_id == "":
32
+ raise MissingArgumentException("client_id")
33
+ if client_secret == "":
34
+ raise MissingArgumentException("client_secret")
35
+ super().__init__(url=api_url, http_client=http_client)
36
+
37
+ self.auth_url = auth_url
38
+ self.expiration_time = None
39
+ self.token = None
40
+ self.oauth2_client = AsyncOAuth2Client(
41
+ client_id, client_secret, scope=auth_scope
42
+ )
43
+
44
+ async def refresh_token(self):
45
+ now = time.time()
46
+ if self.expiration_time is None or self.expiration_time <= now:
47
+ self.token = await self.oauth2_client.fetch_token(self.auth_url)
48
+ self.expiration_time = now + self.token["expires_in"]
49
+
50
+ async def execute(
51
+ self,
52
+ query: str,
53
+ operation_name: Optional[str] = None,
54
+ variables: Optional[Dict[str, Any]] = None,
55
+ **kwargs: Any,
56
+ ) -> httpx.Response:
57
+ await self.refresh_token()
58
+ if self.token is None:
59
+ raise MissingTokenException()
60
+ headers = kwargs.get("headers", {})
61
+ kwargs.update(
62
+ headers={
63
+ "Authorization": f'Bearer {self.token["access_token"]}',
64
+ **headers,
65
+ }
66
+ )
67
+ return await super().execute(query, operation_name, variables, **kwargs)
File without changes
@@ -0,0 +1,51 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Dict, Optional
4
+
5
+
6
+ def get_project_path_relative_to_file(path: str) -> str:
7
+ """
8
+ Get the relative path of a file in the project.
9
+ It is important that this function is in the same directory
10
+ as the commandline entrypoint.
11
+ """
12
+ resolved_path = Path(__file__, path).resolve()
13
+ return os.path.relpath(resolved_path)
14
+
15
+
16
+ def get_codegen_config(
17
+ *,
18
+ schema_path: str,
19
+ queries_path: str,
20
+ target_package_name: str,
21
+ target_package_path: Optional[str] = None
22
+ ) -> Dict:
23
+ """Get the configuration for the codegen tool."""
24
+ return dict(
25
+ tool={
26
+ "ariadne-codegen": dict(
27
+ schema_path=schema_path,
28
+ queries_path=queries_path,
29
+ target_package_name=target_package_name,
30
+ target_package_path=(
31
+ target_package_path if target_package_path else Path.cwd()
32
+ ),
33
+ base_client_name="AsyncFragmentClient",
34
+ base_client_file_path=get_project_path_relative_to_file(
35
+ "../../client/async_client.py"
36
+ ),
37
+ plugins=[
38
+ "fragment.codegen.plugins.get_file_comment.GenerateFileComment",
39
+ "fragment.codegen.plugins.generate_client_method.RewriteUnsetTypeMethodArguments",
40
+ ],
41
+ ),
42
+ },
43
+ )
44
+
45
+
46
+ def get_standard_queries() -> str:
47
+ """Get the standard SDK queries for the codegen tool."""
48
+ standard_query_file_path = get_project_path_relative_to_file(
49
+ "../../std_queries/queries.graphql"
50
+ )
51
+ return Path(standard_query_file_path).read_text()
@@ -0,0 +1,64 @@
1
+ import logging
2
+ import sys
3
+ import tempfile
4
+
5
+ import click
6
+ import httpx
7
+ from ariadne_codegen.main import client as generate_graphql_client
8
+
9
+ from fragment.codegen.helpers import get_codegen_config, get_standard_queries
10
+ from fragment.logger import console_log
11
+
12
+ logging.getLogger("httpx").setLevel(logging.WARNING)
13
+
14
+
15
+ GRAPHQL_SCHEMA_API_URL = "https://api.us-west-2.fragment.dev/schema.graphql"
16
+
17
+
18
+ @click.command()
19
+ @click.option(
20
+ "-i",
21
+ "--input-dir",
22
+ default=None,
23
+ help="Path to your Schema queries",
24
+ required=True,
25
+ )
26
+ @click.option(
27
+ "-n",
28
+ "--target-package-name",
29
+ default="fragment_graphql_client",
30
+ help="The package name for the generated SDK",
31
+ required=False,
32
+ )
33
+ @click.option(
34
+ "-o",
35
+ "--output-dir",
36
+ default=None,
37
+ help="The output directory for the generated SDK. Defaults to CWD.",
38
+ required=False,
39
+ )
40
+ def run(input_dir, target_package_name, output_dir=None):
41
+ console_log.info(f"Downloading the GraphQL schema from {GRAPHQL_SCHEMA_API_URL}")
42
+ try:
43
+ r = httpx.get(GRAPHQL_SCHEMA_API_URL)
44
+ with tempfile.NamedTemporaryFile(
45
+ mode="w"
46
+ ) as schema_file, tempfile.NamedTemporaryFile(
47
+ dir=input_dir, mode="w", suffix=".graphql"
48
+ ) as standard_query_file:
49
+ # Write and flush the most recent schema
50
+ schema_file.write(r.text)
51
+ schema_file.flush()
52
+ # Write and flush the standard queries to the provided input
53
+ standard_query_file.write(get_standard_queries())
54
+ standard_query_file.flush()
55
+ config_dict = get_codegen_config(
56
+ schema_path=schema_file.name,
57
+ queries_path=input_dir,
58
+ target_package_name=target_package_name,
59
+ target_package_path=output_dir,
60
+ )
61
+ generate_graphql_client(config_dict)
62
+ except httpx.RequestError as e:
63
+ console_log.error(f"An error occurred while downloading the schema: {e}")
64
+ sys.exit(1)
File without changes
@@ -0,0 +1,62 @@
1
+ import ast
2
+ from typing import Union
3
+
4
+ from ariadne_codegen.client_generators.constants import (
5
+ OPTIONAL,
6
+ UNION,
7
+ UNSET_NAME,
8
+ UNSET_TYPE_NAME,
9
+ )
10
+ from ariadne_codegen.plugins.base import Plugin
11
+ from graphql import OperationDefinitionNode
12
+
13
+
14
+ def is_ignorable_ast_node(node: Union[ast.expr, None]) -> bool:
15
+ return node is None or isinstance(node, ast.Name)
16
+
17
+
18
+ class RewriteUnsetTypeMethodArguments(Plugin):
19
+ def generate_client_method(
20
+ self,
21
+ method_def: Union[ast.FunctionDef, ast.AsyncFunctionDef],
22
+ operation_definition: OperationDefinitionNode,
23
+ ) -> Union[ast.FunctionDef, ast.AsyncFunctionDef]:
24
+ for idx, arg in enumerate(method_def.args.args):
25
+ if isinstance(arg.annotation, ast.Subscript):
26
+ annotation = arg.annotation
27
+ if annotation.slice is None or not isinstance(
28
+ annotation.slice, ast.Tuple
29
+ ):
30
+ continue
31
+ if (
32
+ not isinstance(annotation.value, ast.Name)
33
+ or annotation.value.id != UNION
34
+ ):
35
+ continue
36
+ subscript, name = annotation.slice.elts
37
+ if (
38
+ isinstance(subscript, ast.Subscript)
39
+ and isinstance(subscript.value, ast.Name)
40
+ and subscript.value.id == OPTIONAL
41
+ and isinstance(name, ast.Name)
42
+ and name.id == UNSET_TYPE_NAME
43
+ ):
44
+ arg.annotation = subscript
45
+ else:
46
+ continue
47
+ elif not is_ignorable_ast_node(arg.annotation):
48
+ raise TypeError(
49
+ f"Expected annotation to be of type Subscript. Got {arg.annotation}"
50
+ )
51
+ if method_def.args.defaults is not None:
52
+ method_def.args.defaults = list(
53
+ map(
54
+ lambda arg: (
55
+ ast.Name(id="None")
56
+ if isinstance(arg, ast.Name) and arg.id == UNSET_NAME
57
+ else arg
58
+ ),
59
+ method_def.args.defaults,
60
+ )
61
+ )
62
+ return method_def
@@ -0,0 +1,18 @@
1
+ from typing import Optional
2
+
3
+ from ariadne_codegen.plugins.base import Plugin
4
+
5
+
6
+ class GenerateFileComment(Plugin):
7
+ def get_file_comment(
8
+ self, comment: str, code: str, source: Optional[str] = None
9
+ ) -> str:
10
+ comment_lines = ["# Generated by fragment (with the help of ariadne-codegen)"]
11
+ codegen_dict = self.config_dict.get("tool", {}).get("ariadne-codegen", {})
12
+ schema_path = codegen_dict.get("schema_path", "")
13
+ queries_path = codegen_dict.get("queries_path", "")
14
+ if source == queries_path:
15
+ comment_lines.append(f"# Source: {source}")
16
+ elif source == schema_path:
17
+ comment_lines.append(f"# Source: schema.graphql")
18
+ return "\n".join(comment_lines)
fragment/exceptions.py ADDED
@@ -0,0 +1,12 @@
1
+ class MissingTokenException(ValueError):
2
+ """Token not found."""
3
+
4
+ def __init__(self):
5
+ super().__init__("Token is None")
6
+
7
+
8
+ class MissingArgumentException(ValueError):
9
+ """Argument not present."""
10
+
11
+ def __init__(self, argument: str):
12
+ super().__init__(f"{argument} must be provided")
fragment/logger.py ADDED
@@ -0,0 +1,6 @@
1
+ import logging
2
+ import sys
3
+
4
+ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
5
+
6
+ console_log = logging.getLogger("console")