ajera 0.1.3__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.
ajera/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from ajera.client import AjeraClient
2
+
3
+ __all__ = [
4
+ "AjeraClient",
5
+ ]
ajera/cli/__init__.py ADDED
@@ -0,0 +1,95 @@
1
+ import logging
2
+ import sys
3
+
4
+ import click
5
+ import requests
6
+
7
+ from ajera.cli.commands import (
8
+ activities,
9
+ bank_accounts,
10
+ clients,
11
+ companies,
12
+ contacts,
13
+ departments,
14
+ employees,
15
+ invoice_formats,
16
+ ledger,
17
+ projects,
18
+ rate_tables,
19
+ vendors,
20
+ )
21
+ from ajera.cli.context import ClientContext
22
+ from ajera.cli.group import CommonClickGroup
23
+
24
+ CLI_DOCS = """
25
+ Deltek Ajera API command-line interface.
26
+
27
+ Connection settings are read from environment variables:
28
+
29
+ \b
30
+ - AJERA_API_URL to define the API endpoint.
31
+ - AJERA_API_USERNAME / AJERA_API_PASSWORD for authentication.
32
+ - AJERA_API_HEADERS as a JSON object of extra request headers.
33
+ """
34
+
35
+
36
+ @click.group(
37
+ cls=CommonClickGroup,
38
+ context_settings={"help_option_names": ["-h", "--help"]},
39
+ help=CLI_DOCS,
40
+ )
41
+ @click.option(
42
+ "--log",
43
+ is_flag=True,
44
+ default=False,
45
+ help="Enable request logging at INFO level.",
46
+ )
47
+ @click.version_option(package_name="ajera", prog_name="ajera")
48
+ @click.pass_context
49
+ def cli(ctx: click.Context, log: bool) -> None:
50
+ ctx.obj = ClientContext(log=log)
51
+
52
+
53
+ # Register all domain-specific command groups.
54
+ # Runtime gating is handled by AJERA_CLI_DISABLE.
55
+ for module in (
56
+ employees,
57
+ clients,
58
+ contacts,
59
+ vendors,
60
+ projects,
61
+ ledger,
62
+ activities,
63
+ companies,
64
+ departments,
65
+ rate_tables,
66
+ bank_accounts,
67
+ invoice_formats,
68
+ ):
69
+ cli.add_command(module.group)
70
+
71
+
72
+ def main() -> None:
73
+ """
74
+ Console-script entry point.
75
+
76
+ Translates expected errors into a non-zero exit with a readable message
77
+ instead of a traceback.
78
+ """
79
+ try:
80
+ cli(standalone_mode=False)
81
+ except click.ClickException as exc:
82
+ exc.show()
83
+ sys.exit(exc.exit_code)
84
+ except click.exceptions.Abort:
85
+ sys.exit(1)
86
+ except requests.HTTPError as exc:
87
+ click.echo(f"Error: {exc}", err=True)
88
+ sys.exit(1)
89
+ except Exception as exc: # noqa: BLE001
90
+ logging.getLogger("ajera").debug("CLI error", exc_info=True)
91
+ click.echo(f"Error: {exc}", err=True)
92
+ sys.exit(1)
93
+
94
+
95
+ __all__ = ["cli", "main"]
ajera/cli/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from ajera.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
File without changes
@@ -0,0 +1,31 @@
1
+ import click
2
+
3
+ from ajera.cli.context import ClientContext
4
+ from ajera.cli.options import status_option
5
+ from ajera.cli.output import render
6
+
7
+
8
+ @click.command(name="activities")
9
+ @status_option
10
+ @click.option(
11
+ "--description-like",
12
+ "filter_by_description_like",
13
+ type=str,
14
+ default=None,
15
+ help="Filter where the description contains this substring.",
16
+ )
17
+ @click.pass_obj
18
+ def group(
19
+ ctx: ClientContext,
20
+ filter_by_status: tuple[str, ...],
21
+ filter_by_description_like: str | None,
22
+ ) -> None:
23
+ """
24
+ List activities (active only by default).
25
+ """
26
+ render(
27
+ ctx.client.list_activities(
28
+ filter_by_status=list(filter_by_status),
29
+ filter_by_description_like=filter_by_description_like,
30
+ )
31
+ )
@@ -0,0 +1,18 @@
1
+ import click
2
+
3
+ from ajera.cli.context import ClientContext
4
+ from ajera.cli.options import status_option
5
+ from ajera.cli.output import render
6
+
7
+
8
+ @click.command(name="bank-accounts")
9
+ @status_option
10
+ @click.pass_obj
11
+ def group(
12
+ ctx: ClientContext,
13
+ filter_by_status: tuple[str, ...],
14
+ ) -> None:
15
+ """
16
+ List bank accounts (active only by default).
17
+ """
18
+ render(ctx.client.list_bank_accounts(filter_by_status=list(filter_by_status)))
@@ -0,0 +1,153 @@
1
+ import click
2
+
3
+ from ajera.cli.context import ClientContext
4
+ from ajera.cli.group import CommonClickGroup
5
+ from ajera.cli.options import status_option
6
+ from ajera.cli.output import render
7
+
8
+
9
+ @click.group(name="clients", cls=CommonClickGroup)
10
+ def group() -> None:
11
+ """
12
+ List and inspect clients.
13
+ """
14
+
15
+
16
+ @group.command(name="list")
17
+ @click.option(
18
+ "--company",
19
+ "filter_by_company",
20
+ type=int,
21
+ multiple=True,
22
+ help="Filter by company ID (repeatable).",
23
+ )
24
+ @status_option
25
+ @click.option(
26
+ "--name-like",
27
+ "filter_by_name_like",
28
+ type=str,
29
+ default=None,
30
+ help="Filter where the client name contains this substring.",
31
+ )
32
+ @click.option(
33
+ "--name-equals",
34
+ "filter_by_name_equals",
35
+ type=str,
36
+ default=None,
37
+ help="Filter where the client name equals this value.",
38
+ )
39
+ @click.option(
40
+ "--client-type",
41
+ "filter_by_client_type",
42
+ type=int,
43
+ multiple=True,
44
+ help="Filter by client type ID (repeatable).",
45
+ )
46
+ @click.option(
47
+ "--modified-after",
48
+ "filter_by_earliest_modified_date",
49
+ type=str,
50
+ default=None,
51
+ help="Earliest modified date (YYYY-MM-DD or ISO 8601).",
52
+ )
53
+ @click.option(
54
+ "--modified-before",
55
+ "filter_by_latest_modified_date",
56
+ type=str,
57
+ default=None,
58
+ help="Latest modified date (YYYY-MM-DD or ISO 8601).",
59
+ )
60
+ @click.pass_obj
61
+ def list_(
62
+ ctx: ClientContext,
63
+ filter_by_company: tuple[int, ...],
64
+ filter_by_status: tuple[str, ...],
65
+ filter_by_name_like: str | None,
66
+ filter_by_name_equals: str | None,
67
+ filter_by_client_type: tuple[int, ...],
68
+ filter_by_earliest_modified_date: str | None,
69
+ filter_by_latest_modified_date: str | None,
70
+ ) -> None:
71
+ """
72
+ List clients (active only by default).
73
+ """
74
+ render(
75
+ ctx.client.list_clients(
76
+ filter_by_company=list(filter_by_company) or None,
77
+ filter_by_status=list(filter_by_status),
78
+ filter_by_name_like=filter_by_name_like,
79
+ filter_by_name_equals=filter_by_name_equals,
80
+ filter_by_client_type=list(filter_by_client_type) or None,
81
+ filter_by_earliest_modified_date=filter_by_earliest_modified_date,
82
+ filter_by_latest_modified_date=filter_by_latest_modified_date,
83
+ )
84
+ )
85
+
86
+
87
+ @group.command(name="get")
88
+ @click.argument("client_ids", nargs=-1, required=True, type=int)
89
+ @click.pass_obj
90
+ def get(ctx: ClientContext, client_ids: tuple[int, ...]) -> None:
91
+ """
92
+ Get one or more clients by ID.
93
+ """
94
+ render(ctx.client.get_clients(list(client_ids)))
95
+
96
+
97
+ @group.command(name="update")
98
+ @click.argument("client_key", type=int)
99
+ @click.option("--description", default=None, help="New client description (name).")
100
+ @click.option("--account-id", default=None, help="New account ID.")
101
+ @click.option("--email", default=None, help="New email address.")
102
+ @click.option("--website", default=None, help="New website URL.")
103
+ @click.option("--primary-phone", default=None, help="New primary phone number.")
104
+ @click.option("--secondary-phone", default=None, help="New secondary phone number.")
105
+ @click.option("--tertiary-phone", default=None, help="New tertiary phone number.")
106
+ @click.option("--fax", default=None, help="New fax number.")
107
+ @click.option("--notes", default=None, help="New notes.")
108
+ @click.pass_obj
109
+ def update(
110
+ ctx: ClientContext,
111
+ client_key: int,
112
+ description: str | None,
113
+ account_id: str | None,
114
+ email: str | None,
115
+ website: str | None,
116
+ primary_phone: str | None,
117
+ secondary_phone: str | None,
118
+ tertiary_phone: str | None,
119
+ fax: str | None,
120
+ notes: str | None,
121
+ ) -> None:
122
+ """
123
+ Update simple fields on one client.
124
+
125
+ Only the options you pass are changed; everything else is left as-is.
126
+ """
127
+ render(
128
+ ctx.client.update_client(
129
+ client_key,
130
+ description=description,
131
+ account_id=account_id,
132
+ email=email,
133
+ website=website,
134
+ primary_phone_number=primary_phone,
135
+ secondary_phone_number=secondary_phone,
136
+ tertiary_phone_number=tertiary_phone,
137
+ fax_number=fax,
138
+ notes=notes,
139
+ )
140
+ )
141
+
142
+
143
+ @group.command(name="types")
144
+ @status_option
145
+ @click.pass_obj
146
+ def types(
147
+ ctx: ClientContext,
148
+ filter_by_status: tuple[str, ...],
149
+ ) -> None:
150
+ """
151
+ List client types (active only by default).
152
+ """
153
+ render(ctx.client.list_client_types(filter_by_status=list(filter_by_status)))
@@ -0,0 +1,18 @@
1
+ import click
2
+
3
+ from ajera.cli.context import ClientContext
4
+ from ajera.cli.options import status_option
5
+ from ajera.cli.output import render
6
+
7
+
8
+ @click.command(name="companies")
9
+ @status_option
10
+ @click.pass_obj
11
+ def group(
12
+ ctx: ClientContext,
13
+ filter_by_status: tuple[str, ...],
14
+ ) -> None:
15
+ """
16
+ List companies (active only by default).
17
+ """
18
+ render(ctx.client.list_companies(filter_by_status=list(filter_by_status)))
@@ -0,0 +1,153 @@
1
+ import click
2
+
3
+ from ajera.cli.context import ClientContext
4
+ from ajera.cli.group import CommonClickGroup
5
+ from ajera.cli.options import status_option
6
+ from ajera.cli.output import render
7
+
8
+
9
+ @click.group(name="contacts", cls=CommonClickGroup)
10
+ def group() -> None:
11
+ """
12
+ List and inspect contacts.
13
+ """
14
+
15
+
16
+ @group.command(name="list")
17
+ @click.option(
18
+ "--company",
19
+ "filter_by_company",
20
+ type=int,
21
+ multiple=True,
22
+ help="Filter by company ID (repeatable).",
23
+ )
24
+ @status_option
25
+ @click.option(
26
+ "--text",
27
+ "filter_by_text",
28
+ type=str,
29
+ default=None,
30
+ help="Filter where the contact text contains this substring.",
31
+ )
32
+ @click.option(
33
+ "--contact-type",
34
+ "filter_by_contact_type",
35
+ type=int,
36
+ multiple=True,
37
+ help="Filter by contact type ID (repeatable).",
38
+ )
39
+ @click.option(
40
+ "--modified-after",
41
+ "filter_by_earliest_modified_date",
42
+ type=str,
43
+ default=None,
44
+ help="Earliest modified date (YYYY-MM-DD or ISO 8601).",
45
+ )
46
+ @click.option(
47
+ "--modified-before",
48
+ "filter_by_latest_modified_date",
49
+ type=str,
50
+ default=None,
51
+ help="Latest modified date (YYYY-MM-DD or ISO 8601).",
52
+ )
53
+ @click.pass_obj
54
+ def list_(
55
+ ctx: ClientContext,
56
+ filter_by_company: tuple[int, ...],
57
+ filter_by_status: tuple[str, ...],
58
+ filter_by_text: str | None,
59
+ filter_by_contact_type: tuple[int, ...],
60
+ filter_by_earliest_modified_date: str | None,
61
+ filter_by_latest_modified_date: str | None,
62
+ ) -> None:
63
+ """
64
+ List contacts (active only by default).
65
+ """
66
+ render(
67
+ ctx.client.list_contacts(
68
+ filter_by_company=list(filter_by_company) or None,
69
+ filter_by_status=list(filter_by_status),
70
+ filter_by_text=filter_by_text,
71
+ filter_by_contact_type=list(filter_by_contact_type) or None,
72
+ filter_by_earliest_modified_date=filter_by_earliest_modified_date,
73
+ filter_by_latest_modified_date=filter_by_latest_modified_date,
74
+ )
75
+ )
76
+
77
+
78
+ @group.command(name="get")
79
+ @click.argument("contact_ids", nargs=-1, required=True, type=int)
80
+ @click.pass_obj
81
+ def get(ctx: ClientContext, contact_ids: tuple[int, ...]) -> None:
82
+ """
83
+ Get one or more contacts by ID.
84
+ """
85
+ render(ctx.client.get_contacts(list(contact_ids)))
86
+
87
+
88
+ @group.command(name="update")
89
+ @click.argument("contact_key", type=int)
90
+ @click.option("--first-name", default=None, help="New first name.")
91
+ @click.option("--middle-name", default=None, help="New middle name.")
92
+ @click.option("--last-name", default=None, help="New last name.")
93
+ @click.option("--title", default=None, help="New title.")
94
+ @click.option("--company", default=None, help="New company.")
95
+ @click.option("--email", default=None, help="New email address.")
96
+ @click.option("--website", default=None, help="New website URL.")
97
+ @click.option("--primary-phone", default=None, help="New primary phone number.")
98
+ @click.option("--secondary-phone", default=None, help="New secondary phone number.")
99
+ @click.option("--tertiary-phone", default=None, help="New tertiary phone number.")
100
+ @click.option("--fax", default=None, help="New fax number.")
101
+ @click.option("--notes", default=None, help="New notes.")
102
+ @click.pass_obj
103
+ def update(
104
+ ctx: ClientContext,
105
+ contact_key: int,
106
+ first_name: str | None,
107
+ middle_name: str | None,
108
+ last_name: str | None,
109
+ title: str | None,
110
+ company: str | None,
111
+ email: str | None,
112
+ website: str | None,
113
+ primary_phone: str | None,
114
+ secondary_phone: str | None,
115
+ tertiary_phone: str | None,
116
+ fax: str | None,
117
+ notes: str | None,
118
+ ) -> None:
119
+ """
120
+ Update simple fields on one contact.
121
+
122
+ Only the options you pass are changed; everything else is left as-is.
123
+ """
124
+ render(
125
+ ctx.client.update_contact(
126
+ contact_key,
127
+ first_name=first_name,
128
+ middle_name=middle_name,
129
+ last_name=last_name,
130
+ title=title,
131
+ company=company,
132
+ email=email,
133
+ website=website,
134
+ primary_phone_number=primary_phone,
135
+ secondary_phone_number=secondary_phone,
136
+ tertiary_phone_number=tertiary_phone,
137
+ fax_number=fax,
138
+ notes=notes,
139
+ )
140
+ )
141
+
142
+
143
+ @group.command(name="types")
144
+ @status_option
145
+ @click.pass_obj
146
+ def types(
147
+ ctx: ClientContext,
148
+ filter_by_status: tuple[str, ...],
149
+ ) -> None:
150
+ """
151
+ List contact types (active only by default).
152
+ """
153
+ render(ctx.client.list_contact_types(filter_by_status=list(filter_by_status)))
@@ -0,0 +1,18 @@
1
+ import click
2
+
3
+ from ajera.cli.context import ClientContext
4
+ from ajera.cli.options import status_option
5
+ from ajera.cli.output import render
6
+
7
+
8
+ @click.command(name="departments")
9
+ @status_option
10
+ @click.pass_obj
11
+ def group(
12
+ ctx: ClientContext,
13
+ filter_by_status: tuple[str, ...],
14
+ ) -> None:
15
+ """
16
+ List departments (active only by default).
17
+ """
18
+ render(ctx.client.list_departments(filter_by_status=list(filter_by_status)))