diff --git a/generators/python-v2/dynamic-snippets/src/EndpointSnippetGenerator.ts b/generators/python-v2/dynamic-snippets/src/EndpointSnippetGenerator.ts index c6a08dee8fbc..5e35b2d0bcce 100644 --- a/generators/python-v2/dynamic-snippets/src/EndpointSnippetGenerator.ts +++ b/generators/python-v2/dynamic-snippets/src/EndpointSnippetGenerator.ts @@ -293,16 +293,24 @@ export class EndpointSnippetGenerator { auth: FernIr.dynamic.BasicAuth; values: FernIr.dynamic.BasicAuthValues; }): python.NamedValue[] { - return [ - { + // usernameOmit/passwordOmit may exist in newer IR versions + const authRecord = auth as unknown as Record; + const usernameOmitted = !!authRecord.usernameOmit; + const passwordOmitted = !!authRecord.passwordOmit; + const args: python.NamedValue[] = []; + if (!usernameOmitted) { + args.push({ name: this.context.getPropertyName(auth.username), value: python.TypeInstantiation.str(values.username) - }, - { + }); + } + if (!passwordOmitted) { + args.push({ name: this.context.getPropertyName(auth.password), value: python.TypeInstantiation.str(values.password) - } - ]; + }); + } + return args; } private getConstructorBearerAuthArgs({ diff --git a/generators/python/sdk/versions.yml b/generators/python/sdk/versions.yml index f3174b68b8a8..f289dd409d37 100644 --- a/generators/python/sdk/versions.yml +++ b/generators/python/sdk/versions.yml @@ -1,5 +1,17 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json # For unreleased changes, use unreleased.yml +- version: 5.4.0 + changelogEntry: + - summary: | + Support omitting username or password from basic auth when configured via + `usernameOmit` or `passwordOmit` in the IR. Omitted fields are removed from + the SDK's public API and treated as empty strings internally (e.g., omitting + password encodes `username:`, omitting username encodes `:password`). When + both are omitted, the Authorization header is skipped entirely. + type: feat + createdAt: "2026-04-03" + irVersion: 65 + - version: 5.3.1 changelogEntry: - summary: | @@ -40,7 +52,6 @@ type: feat createdAt: "2026-03-31" irVersion: 65 - - version: 5.1.3 changelogEntry: - summary: | diff --git a/generators/python/src/fern_python/generators/sdk/core_utilities/client_wrapper_generator.py b/generators/python/src/fern_python/generators/sdk/core_utilities/client_wrapper_generator.py index 1b22151cb9d2..1b196be4d53b 100644 --- a/generators/python/src/fern_python/generators/sdk/core_utilities/client_wrapper_generator.py +++ b/generators/python/src/fern_python/generators/sdk/core_utilities/client_wrapper_generator.py @@ -523,33 +523,59 @@ def _write_get_headers_body(writer: AST.NodeWriter) -> None: writer.write_newline_if_last_line_not() basic_auth_scheme = self._get_basic_auth_scheme() if basic_auth_scheme is not None: + username_omitted = getattr(basic_auth_scheme, "username_omit", None) is True + password_omitted = getattr(basic_auth_scheme, "password_omit", None) is True + if not self._context.ir.sdk_config.is_auth_mandatory: - username_var = names.get_username_constructor_parameter_name(basic_auth_scheme) - password_var = names.get_password_constructor_parameter_name(basic_auth_scheme) - writer.write_line(f"{username_var} = self.{names.get_username_getter_name(basic_auth_scheme)}()") - writer.write_line(f"{password_var} = self.{names.get_password_getter_name(basic_auth_scheme)}()") - writer.write_line(f"if {username_var} is not None and {password_var} is not None:") - with writer.indent(): - writer.write(f'headers["{ClientWrapperGenerator.AUTHORIZATION_HEADER}"] = ') - writer.write_node( - AST.ClassInstantiation( - class_=httpx.HttpX.BASIC_AUTH, - args=[ - AST.Expression(f"{username_var}"), - AST.Expression(f"{password_var}"), - ], - ) + # Build condition and args based on which fields are omitted vs present + conditions = [] + if not username_omitted: + username_var = names.get_username_constructor_parameter_name(basic_auth_scheme) + writer.write_line( + f"{username_var} = self.{names.get_username_getter_name(basic_auth_scheme)}()" + ) + conditions.append(f"{username_var} is not None") + if not password_omitted: + password_var = names.get_password_constructor_parameter_name(basic_auth_scheme) + writer.write_line( + f"{password_var} = self.{names.get_password_getter_name(basic_auth_scheme)}()" ) - writer.write("._auth_header") - writer.write_newline_if_last_line_not() + conditions.append(f"{password_var} is not None") + + # Omitted fields use empty string directly + username_arg = AST.Expression('""') if username_omitted else AST.Expression(username_var) + password_arg = AST.Expression('""') if password_omitted else AST.Expression(password_var) + + if conditions: + writer.write_line(f"if {' and '.join(conditions)}:") + with writer.indent(): + writer.write(f'headers["{ClientWrapperGenerator.AUTHORIZATION_HEADER}"] = ') + writer.write_node( + AST.ClassInstantiation( + class_=httpx.HttpX.BASIC_AUTH, + args=[username_arg, password_arg], + ) + ) + writer.write("._auth_header") + writer.write_newline_if_last_line_not() + else: + # Both fields omitted and auth is non-mandatory - skip header entirely + pass else: + # Auth is mandatory - omitted fields use empty string + username_getter = ( + '""' if username_omitted else f"self.{names.get_username_getter_name(basic_auth_scheme)}()" + ) + password_getter = ( + '""' if password_omitted else f"self.{names.get_password_getter_name(basic_auth_scheme)}()" + ) writer.write(f'headers["{ClientWrapperGenerator.AUTHORIZATION_HEADER}"] = ') writer.write_node( AST.ClassInstantiation( class_=httpx.HttpX.BASIC_AUTH, args=[ - AST.Expression(f"self.{names.get_username_getter_name(basic_auth_scheme)}()"), - AST.Expression(f"self.{names.get_password_getter_name(basic_auth_scheme)}()"), + AST.Expression(username_getter), + AST.Expression(password_getter), ], ) ) @@ -802,110 +828,114 @@ def _get_constructor_info(self, exclude_auth: bool = False) -> ConstructorInfo: basic_auth_scheme = self._get_basic_auth_scheme() if basic_auth_scheme is not None: - username_constructor_parameter_name = names.get_username_constructor_parameter_name(basic_auth_scheme) - username_constructor_parameter = ConstructorParameter( - constructor_parameter_name=username_constructor_parameter_name, - private_member_name=names.get_username_member_name(basic_auth_scheme), - type_hint=( - ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT - if self._context.ir.sdk_config.is_auth_mandatory - else AST.TypeHint.optional(ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT) - ), - initializer=AST.Expression( - f'{username_constructor_parameter_name}="YOUR_{basic_auth_scheme.username.screaming_snake_case.safe_name}"', - ), - getter_method=AST.FunctionDeclaration( - name=names.get_username_getter_name(basic_auth_scheme), - signature=AST.FunctionSignature( - parameters=[], - return_type=( - AST.TypeHint.str_() - if self._context.ir.sdk_config.is_auth_mandatory - else AST.TypeHint.optional(AST.TypeHint.str_()) - ), - ), - body=AST.CodeWriter( - self._get_required_getter_body_writer( - member_name=names.get_username_member_name(basic_auth_scheme) - ) + username_omitted = getattr(basic_auth_scheme, "username_omit", None) is True + password_omitted = getattr(basic_auth_scheme, "password_omit", None) is True + + # When omit is true, the field is completely removed from the end-user API. + # Only add non-omitted fields to constructor parameters. + if not username_omitted: + username_constructor_parameter_name = names.get_username_constructor_parameter_name(basic_auth_scheme) + username_constructor_parameter = ConstructorParameter( + constructor_parameter_name=username_constructor_parameter_name, + private_member_name=names.get_username_member_name(basic_auth_scheme), + type_hint=( + ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT if self._context.ir.sdk_config.is_auth_mandatory - else self._get_optional_getter_body_writer( - member_name=names.get_username_member_name(basic_auth_scheme) - ) + else AST.TypeHint.optional(ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT) ), - ), - environment_variable=( - basic_auth_scheme.username_env_var if basic_auth_scheme.username_env_var is not None else None - ), - is_basic=True, - template=TemplateGenerator.string_template( - is_optional=False, - template_string_prefix=username_constructor_parameter_name, - inputs=[ - TemplateInput.factory.payload( - PayloadInput( - location="AUTH", - path="username", - ) + initializer=AST.Expression( + f'{username_constructor_parameter_name}="YOUR_{basic_auth_scheme.username.screaming_snake_case.safe_name}"', + ), + getter_method=AST.FunctionDeclaration( + name=names.get_username_getter_name(basic_auth_scheme), + signature=AST.FunctionSignature( + parameters=[], + return_type=( + AST.TypeHint.str_() + if self._context.ir.sdk_config.is_auth_mandatory + else AST.TypeHint.optional(AST.TypeHint.str_()) + ), ), - ], - ), - ) - password_constructor_parameter_name = names.get_password_constructor_parameter_name(basic_auth_scheme) - password_constructor_parameter = ConstructorParameter( - constructor_parameter_name=password_constructor_parameter_name, - private_member_name=names.get_password_member_name(basic_auth_scheme), - type_hint=( - ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT - if self._context.ir.sdk_config.is_auth_mandatory - else AST.TypeHint.optional(ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT) - ), - initializer=AST.Expression( - f'{password_constructor_parameter_name}="YOUR_{basic_auth_scheme.password.screaming_snake_case.safe_name}"', - ), - getter_method=AST.FunctionDeclaration( - name=names.get_password_getter_name(basic_auth_scheme), - signature=AST.FunctionSignature( - parameters=[], - return_type=( - AST.TypeHint.str_() + body=AST.CodeWriter( + self._get_required_getter_body_writer( + member_name=names.get_username_member_name(basic_auth_scheme) + ) if self._context.ir.sdk_config.is_auth_mandatory - else AST.TypeHint.optional(AST.TypeHint.str_()) + else self._get_optional_getter_body_writer( + member_name=names.get_username_member_name(basic_auth_scheme) + ) ), ), - body=AST.CodeWriter( - self._get_required_getter_body_writer( - member_name=names.get_password_member_name(basic_auth_scheme) - ) + environment_variable=( + basic_auth_scheme.username_env_var if basic_auth_scheme.username_env_var is not None else None + ), + is_basic=True, + template=TemplateGenerator.string_template( + is_optional=False, + template_string_prefix=username_constructor_parameter_name, + inputs=[ + TemplateInput.factory.payload( + PayloadInput( + location="AUTH", + path="username", + ) + ), + ], + ), + ) + parameters.append(username_constructor_parameter) + + if not password_omitted: + password_constructor_parameter_name = names.get_password_constructor_parameter_name(basic_auth_scheme) + password_constructor_parameter = ConstructorParameter( + constructor_parameter_name=password_constructor_parameter_name, + private_member_name=names.get_password_member_name(basic_auth_scheme), + type_hint=( + ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT if self._context.ir.sdk_config.is_auth_mandatory - else self._get_optional_getter_body_writer( - member_name=names.get_password_member_name(basic_auth_scheme) - ) + else AST.TypeHint.optional(ClientWrapperGenerator.STRING_OR_SUPPLIER_TYPE_HINT) ), - ), - is_basic=True, - environment_variable=( - basic_auth_scheme.password_env_var if basic_auth_scheme.password_env_var is not None else None - ), - template=TemplateGenerator.string_template( - is_optional=False, - template_string_prefix=password_constructor_parameter_name, - inputs=[ - TemplateInput.factory.payload( - PayloadInput( - location="AUTH", - path="password", + initializer=AST.Expression( + f'{password_constructor_parameter_name}="YOUR_{basic_auth_scheme.password.screaming_snake_case.safe_name}"', + ), + getter_method=AST.FunctionDeclaration( + name=names.get_password_getter_name(basic_auth_scheme), + signature=AST.FunctionSignature( + parameters=[], + return_type=( + AST.TypeHint.str_() + if self._context.ir.sdk_config.is_auth_mandatory + else AST.TypeHint.optional(AST.TypeHint.str_()) + ), + ), + body=AST.CodeWriter( + self._get_required_getter_body_writer( + member_name=names.get_password_member_name(basic_auth_scheme) + ) + if self._context.ir.sdk_config.is_auth_mandatory + else self._get_optional_getter_body_writer( + member_name=names.get_password_member_name(basic_auth_scheme) ) ), - ], - ), - ) - parameters.extend( - [ - username_constructor_parameter, - password_constructor_parameter, - ] - ) + ), + is_basic=True, + environment_variable=( + basic_auth_scheme.password_env_var if basic_auth_scheme.password_env_var is not None else None + ), + template=TemplateGenerator.string_template( + is_optional=False, + template_string_prefix=password_constructor_parameter_name, + inputs=[ + TemplateInput.factory.payload( + PayloadInput( + location="AUTH", + path="password", + ) + ), + ], + ), + ) + parameters.append(password_constructor_parameter) # Add generic headers parameter parameters.append(headers_constructor_parameter) diff --git a/packages/cli/generation/ir-generator/src/dynamic-snippets/DynamicSnippetsConverter.ts b/packages/cli/generation/ir-generator/src/dynamic-snippets/DynamicSnippetsConverter.ts index b1750ea01c28..5675bc14999b 100644 --- a/packages/cli/generation/ir-generator/src/dynamic-snippets/DynamicSnippetsConverter.ts +++ b/packages/cli/generation/ir-generator/src/dynamic-snippets/DynamicSnippetsConverter.ts @@ -732,11 +732,22 @@ export class DynamicSnippetsConverter { } const scheme = auth.schemes[0]; switch (scheme.type) { - case "basic": - return DynamicSnippets.Auth.basic({ + case "basic": { + const basicAuth: DynamicSnippets.BasicAuth & { + usernameOmit?: boolean; + passwordOmit?: boolean; + } = { username: this.inflateName(scheme.username), password: this.inflateName(scheme.password) - }); + }; + if (scheme.usernameOmit) { + basicAuth.usernameOmit = scheme.usernameOmit; + } + if (scheme.passwordOmit) { + basicAuth.passwordOmit = scheme.passwordOmit; + } + return DynamicSnippets.Auth.basic(basicAuth); + } case "bearer": return DynamicSnippets.Auth.bearer({ token: this.inflateName(scheme.token) diff --git a/seed/python-sdk/basic-auth-pw-omitted/snippet.json b/seed/python-sdk/basic-auth-pw-omitted/snippet.json index 4287155ce181..0d3ccd66b2e0 100644 --- a/seed/python-sdk/basic-auth-pw-omitted/snippet.json +++ b/seed/python-sdk/basic-auth-pw-omitted/snippet.json @@ -9,8 +9,8 @@ "identifier_override": "endpoint_basic-auth.getWithBasicAuth" }, "snippet": { - "sync_client": "from seed import SeedBasicAuthPwOmitted\n\nclient = SeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n password=\"YOUR_PASSWORD\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.basic_auth.get_with_basic_auth()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedBasicAuthPwOmitted\n\nclient = AsyncSeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n password=\"YOUR_PASSWORD\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.basic_auth.get_with_basic_auth()\n\n\nasyncio.run(main())\n", + "sync_client": "from seed import SeedBasicAuthPwOmitted\n\nclient = SeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.basic_auth.get_with_basic_auth()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedBasicAuthPwOmitted\n\nclient = AsyncSeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.basic_auth.get_with_basic_auth()\n\n\nasyncio.run(main())\n", "type": "python" } }, @@ -22,10 +22,10 @@ "identifier_override": "endpoint_basic-auth.postWithBasicAuth" }, "snippet": { - "sync_client": "from seed import SeedBasicAuthPwOmitted\n\nclient = SeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n password=\"YOUR_PASSWORD\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.basic_auth.post_with_basic_auth(\n request={\"key\": \"value\"},\n)\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedBasicAuthPwOmitted\n\nclient = AsyncSeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n password=\"YOUR_PASSWORD\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.basic_auth.post_with_basic_auth(\n request={\"key\": \"value\"},\n )\n\n\nasyncio.run(main())\n", + "sync_client": "from seed import SeedBasicAuthPwOmitted\n\nclient = SeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.basic_auth.post_with_basic_auth(\n request={\"key\": \"value\"},\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedBasicAuthPwOmitted\n\nclient = AsyncSeedBasicAuthPwOmitted(\n username=\"YOUR_USERNAME\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.basic_auth.post_with_basic_auth(\n request={\"key\": \"value\"},\n )\n\n\nasyncio.run(main())\n", "type": "python" } } ] -} \ No newline at end of file +} diff --git a/seed/python-sdk/basic-auth-pw-omitted/src/seed/__init__.py b/seed/python-sdk/basic-auth-pw-omitted/src/seed/__init__.py index 39aa5390f2ae..8c924d9325c3 100644 --- a/seed/python-sdk/basic-auth-pw-omitted/src/seed/__init__.py +++ b/seed/python-sdk/basic-auth-pw-omitted/src/seed/__init__.py @@ -8,14 +8,11 @@ if typing.TYPE_CHECKING: from .errors import BadRequest, UnauthorizedRequest, UnauthorizedRequestErrorBody from . import basic_auth, errors - from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient from .client import AsyncSeedBasicAuthPwOmitted, SeedBasicAuthPwOmitted from .version import __version__ _dynamic_imports: typing.Dict[str, str] = { "AsyncSeedBasicAuthPwOmitted": ".client", "BadRequest": ".errors", - "DefaultAioHttpClient": "._default_clients", - "DefaultAsyncHttpxClient": "._default_clients", "SeedBasicAuthPwOmitted": ".client", "UnauthorizedRequest": ".errors", "UnauthorizedRequestErrorBody": ".errors", @@ -49,8 +46,6 @@ def __dir__(): __all__ = [ "AsyncSeedBasicAuthPwOmitted", "BadRequest", - "DefaultAioHttpClient", - "DefaultAsyncHttpxClient", "SeedBasicAuthPwOmitted", "UnauthorizedRequest", "UnauthorizedRequestErrorBody", diff --git a/seed/python-sdk/basic-auth-pw-omitted/src/seed/client.py b/seed/python-sdk/basic-auth-pw-omitted/src/seed/client.py index 45141156dd72..05e7158f26cd 100644 --- a/seed/python-sdk/basic-auth-pw-omitted/src/seed/client.py +++ b/seed/python-sdk/basic-auth-pw-omitted/src/seed/client.py @@ -22,7 +22,7 @@ class SeedBasicAuthPwOmitted: The base url to use for requests from the client. username : typing.Union[str, typing.Callable[[], str]] - password : typing.Union[str, typing.Callable[[], str]] + password : typing.Optional[typing.Union[str, typing.Callable[[], str]]] headers : typing.Optional[typing.Dict[str, str]] Additional headers to send with every request. @@ -54,7 +54,7 @@ def __init__( *, base_url: str, username: typing.Union[str, typing.Callable[[], str]], - password: typing.Union[str, typing.Callable[[], str]], + password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, follow_redirects: typing.Optional[bool] = True, @@ -88,24 +88,6 @@ def basic_auth(self): return self._basic_auth -def _make_default_async_client( - timeout: typing.Optional[float], - follow_redirects: typing.Optional[bool], -) -> httpx.AsyncClient: - try: - import httpx_aiohttp # type: ignore[import-not-found] - except ImportError: - pass - else: - if follow_redirects is not None: - return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) - return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) - - if follow_redirects is not None: - return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) - return httpx.AsyncClient(timeout=timeout) - - class AsyncSeedBasicAuthPwOmitted: """ Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. @@ -116,7 +98,7 @@ class AsyncSeedBasicAuthPwOmitted: The base url to use for requests from the client. username : typing.Union[str, typing.Callable[[], str]] - password : typing.Union[str, typing.Callable[[], str]] + password : typing.Optional[typing.Union[str, typing.Callable[[], str]]] headers : typing.Optional[typing.Dict[str, str]] Additional headers to send with every request. @@ -148,7 +130,7 @@ def __init__( *, base_url: str, username: typing.Union[str, typing.Callable[[], str]], - password: typing.Union[str, typing.Callable[[], str]], + password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, follow_redirects: typing.Optional[bool] = True, @@ -165,7 +147,9 @@ def __init__( headers=headers, httpx_client=httpx_client if httpx_client is not None - else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), + else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.AsyncClient(timeout=_defaulted_timeout), timeout=_defaulted_timeout, logging=logging, ) diff --git a/seed/python-sdk/basic-auth-pw-omitted/src/seed/core/client_wrapper.py b/seed/python-sdk/basic-auth-pw-omitted/src/seed/core/client_wrapper.py index 8778a3f34646..fb36028c9dc9 100644 --- a/seed/python-sdk/basic-auth-pw-omitted/src/seed/core/client_wrapper.py +++ b/seed/python-sdk/basic-auth-pw-omitted/src/seed/core/client_wrapper.py @@ -12,7 +12,7 @@ def __init__( self, *, username: typing.Union[str, typing.Callable[[], str]], - password: typing.Union[str, typing.Callable[[], str]], + password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, @@ -37,7 +37,7 @@ def get_headers(self) -> typing.Dict[str, str]: "X-Fern-SDK-Version": "0.0.1", **(self.get_custom_headers() or {}), } - headers["Authorization"] = httpx.BasicAuth(self._get_username(), self._get_password())._auth_header + headers["Authorization"] = httpx.BasicAuth(self._get_username(), self._get_password() or "")._auth_header return headers def _get_username(self) -> str: @@ -46,8 +46,8 @@ def _get_username(self) -> str: else: return self._username() - def _get_password(self) -> str: - if isinstance(self._password, str): + def _get_password(self) -> typing.Optional[str]: + if isinstance(self._password, str) or self._password is None: return self._password else: return self._password() @@ -67,7 +67,7 @@ def __init__( self, *, username: typing.Union[str, typing.Callable[[], str]], - password: typing.Union[str, typing.Callable[[], str]], + password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, @@ -91,7 +91,7 @@ def __init__( self, *, username: typing.Union[str, typing.Callable[[], str]], - password: typing.Union[str, typing.Callable[[], str]], + password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None,