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.
- fragment/__init__.py +0 -0
- fragment/client/__init__.py +1 -0
- fragment/client/async_client.py +67 -0
- fragment/codegen/__init__.py +0 -0
- fragment/codegen/helpers.py +51 -0
- fragment/codegen/main.py +64 -0
- fragment/codegen/plugins/__init__.py +0 -0
- fragment/codegen/plugins/generate_client_method.py +62 -0
- fragment/codegen/plugins/get_file_comment.py +18 -0
- fragment/exceptions.py +12 -0
- fragment/logger.py +6 -0
- fragment/sdk/__init__.py +478 -0
- fragment/sdk/add_ledger_entry.py +54 -0
- fragment/sdk/add_ledger_entry_runtime.py +54 -0
- fragment/sdk/async_client.py +69 -0
- fragment/sdk/base_model.py +29 -0
- fragment/sdk/client.py +1071 -0
- fragment/sdk/create_custom_link.py +40 -0
- fragment/sdk/create_ledger.py +49 -0
- fragment/sdk/enums.py +263 -0
- fragment/sdk/get_ledger.py +23 -0
- fragment/sdk/get_ledger_account_balance.py +23 -0
- fragment/sdk/get_ledger_account_lines.py +47 -0
- fragment/sdk/get_ledger_entry.py +41 -0
- fragment/sdk/get_schema.py +28 -0
- fragment/sdk/get_workspace.py +16 -0
- fragment/sdk/input_types.py +482 -0
- fragment/sdk/list_ledger_account_balances.py +53 -0
- fragment/sdk/list_ledger_accounts.py +50 -0
- fragment/sdk/list_ledger_entries.py +58 -0
- fragment/sdk/list_ledger_entry_group_balances.py +57 -0
- fragment/sdk/list_multi_currency_ledger_account_balances.py +130 -0
- fragment/sdk/reconcile_tx.py +55 -0
- fragment/sdk/reconcile_tx_runtime.py +55 -0
- fragment/sdk/store_schema.py +45 -0
- fragment/sdk/sync_custom_accounts.py +53 -0
- fragment/sdk/sync_custom_txs.py +44 -0
- fragment/sdk/update_ledger.py +39 -0
- fragment/sdk/update_ledger_entry.py +77 -0
- fragment/std_queries/queries.graphql +656 -0
- fragment_python-0.1.1.dist-info/LICENSE +201 -0
- fragment_python-0.1.1.dist-info/METADATA +142 -0
- fragment_python-0.1.1.dist-info/RECORD +45 -0
- fragment_python-0.1.1.dist-info/WHEEL +4 -0
- 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()
|
fragment/codegen/main.py
ADDED
|
@@ -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")
|