diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2601677..d0ab664 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.1.0" + ".": "1.2.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 92721c7..06e7614 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 5 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser%2Fcas-parser-b7fdba3d3f97c7debc22c7ca30b828bce81bcd64648df8c94029b27a3321ebb9.yml -openapi_spec_hash: 03f1315f1d32ada42445ca920f047dff +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser%2Fcas-parser-9eaed98ce5934f11e901cef376a28257d2c196bd3dba7c690babc6741a730ded.yml +openapi_spec_hash: b76e4e830c4d03ba4cf9429bb9fb9c8a config_hash: cb5d75abef6264b5d86448caf7295afa diff --git a/CHANGELOG.md b/CHANGELOG.md index eff9d05..272df55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 1.2.0 (2025-10-11) + +Full Changelog: [v1.1.0...v1.2.0](https://github.com/CASParser/cas-parser-python/compare/v1.1.0...v1.2.0) + +### Features + +* **api:** api update ([f1838dc](https://github.com/CASParser/cas-parser-python/commit/f1838dcb901635626cc87cb55dfaa4ef33ba5092)) + + +### Chores + +* do not install brew dependencies in ./scripts/bootstrap by default ([35b17eb](https://github.com/CASParser/cas-parser-python/commit/35b17eb26264ab66e24b074bcb1790f6c33b7b9c)) +* **internal:** detect missing future annotations with ruff ([8c35489](https://github.com/CASParser/cas-parser-python/commit/8c354893c00887af1da9c197dc21dd4d6f0033af)) +* **internal:** update pydantic dependency ([1c3104b](https://github.com/CASParser/cas-parser-python/commit/1c3104b27350f4c906973bb56f89d5a16f55d35e)) +* **types:** change optional parameter type from NotGiven to Omit ([e739e12](https://github.com/CASParser/cas-parser-python/commit/e739e12ade4f91e52f0285c866354e970195aacf)) + ## 1.1.0 (2025-09-06) Full Changelog: [v1.0.2...v1.1.0](https://github.com/CASParser/cas-parser-python/compare/v1.0.2...v1.1.0) diff --git a/pyproject.toml b/pyproject.toml index 33ccf0d..d88131c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cas-parser-python" -version = "1.1.0" +version = "1.2.0" description = "The official Python library for the CAS Parser API" dynamic = ["readme"] license = "Apache-2.0" @@ -224,6 +224,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -246,6 +248,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" diff --git a/requirements-dev.lock b/requirements-dev.lock index d000467..cd93fa9 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -88,9 +88,9 @@ pluggy==1.5.0 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via cas-parser-python -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic pygments==2.18.0 # via rich @@ -126,6 +126,9 @@ typing-extensions==4.12.2 # via pydantic # via pydantic-core # via pyright + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic virtualenv==20.24.5 # via nox yarl==1.20.0 diff --git a/requirements.lock b/requirements.lock index 46d36df..c02016b 100644 --- a/requirements.lock +++ b/requirements.lock @@ -55,9 +55,9 @@ multidict==6.4.4 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via cas-parser-python -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic sniffio==1.3.0 # via anyio @@ -68,5 +68,8 @@ typing-extensions==4.12.2 # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic yarl==1.20.0 # via aiohttp diff --git a/scripts/bootstrap b/scripts/bootstrap index e84fe62..b430fee 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi diff --git a/src/cas_parser/__init__.py b/src/cas_parser/__init__.py index a6c342f..1e1d246 100644 --- a/src/cas_parser/__init__.py +++ b/src/cas_parser/__init__.py @@ -3,7 +3,7 @@ import typing as _t from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import ( Client, @@ -48,7 +48,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", "Omit", + "omit", "CasParserError", "APIError", "APIStatusError", diff --git a/src/cas_parser/_base_client.py b/src/cas_parser/_base_client.py index 8a47ab7..fc89c5a 100644 --- a/src/cas_parser/_base_client.py +++ b/src/cas_parser/_base_client.py @@ -42,7 +42,6 @@ from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -57,6 +56,7 @@ RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping from ._compat import PYDANTIC_V1, model_copy, model_dump @@ -145,9 +145,9 @@ def __init__( def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - json: Body | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url self.json = json @@ -595,7 +595,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -825,7 +825,7 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1356,7 +1356,7 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1818,8 +1818,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} diff --git a/src/cas_parser/_client.py b/src/cas_parser/_client.py index 27572c6..19a598a 100644 --- a/src/cas_parser/_client.py +++ b/src/cas_parser/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Union, Mapping +from typing import Any, Mapping from typing_extensions import Self, override import httpx @@ -11,13 +11,13 @@ from . import _exceptions from ._qs import Querystring from ._types import ( - NOT_GIVEN, Omit, Timeout, NotGiven, Transport, ProxiesTypes, RequestOptions, + not_given, ) from ._utils import is_given, get_async_library from ._version import __version__ @@ -56,7 +56,7 @@ def __init__( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -132,9 +132,9 @@ def copy( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -226,7 +226,7 @@ def __init__( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -302,9 +302,9 @@ def copy( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, diff --git a/src/cas_parser/_models.py b/src/cas_parser/_models.py index 3a6017e..6a3cd1d 100644 --- a/src/cas_parser/_models.py +++ b/src/cas_parser/_models.py @@ -256,7 +256,7 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, @@ -264,6 +264,7 @@ def model_dump( warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, serialize_as_any: bool = False, + fallback: Callable[[Any], Any] | None = None, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -295,10 +296,12 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, @@ -313,13 +316,14 @@ def model_dump_json( indent: int | None = None, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -348,11 +352,13 @@ def model_dump_json( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, diff --git a/src/cas_parser/_qs.py b/src/cas_parser/_qs.py index 274320c..ada6fd3 100644 --- a/src/cas_parser/_qs.py +++ b/src/cas_parser/_qs.py @@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode from typing_extensions import Literal, get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, not_given from ._utils import flatten _T = TypeVar("_T") @@ -41,8 +41,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +56,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -143,8 +143,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/cas_parser/_types.py b/src/cas_parser/_types.py index 920e967..b45034b 100644 --- a/src/cas_parser/_types.py +++ b/src/cas_parser/_types.py @@ -117,18 +117,21 @@ class RequestOptions(TypedDict, total=False): # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -140,13 +143,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -156,8 +160,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -165,6 +169,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod diff --git a/src/cas_parser/_utils/_transform.py b/src/cas_parser/_utils/_transform.py index c19124f..5207549 100644 --- a/src/cas_parser/_utils/_transform.py +++ b/src/cas_parser/_utils/_transform.py @@ -268,7 +268,7 @@ def _transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue @@ -434,7 +434,7 @@ async def _async_transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue diff --git a/src/cas_parser/_utils/_utils.py b/src/cas_parser/_utils/_utils.py index f081859..50d5926 100644 --- a/src/cas_parser/_utils/_utils.py +++ b/src/cas_parser/_utils/_utils.py @@ -21,7 +21,7 @@ import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike +from .._types import Omit, NotGiven, FileTypes, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -63,7 +63,7 @@ def _extract_items( try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -126,8 +126,8 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. diff --git a/src/cas_parser/_version.py b/src/cas_parser/_version.py index 69821a2..6ae1318 100644 --- a/src/cas_parser/_version.py +++ b/src/cas_parser/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "cas_parser" -__version__ = "1.1.0" # x-release-please-version +__version__ = "1.2.0" # x-release-please-version diff --git a/src/cas_parser/resources/cas_generator.py b/src/cas_parser/resources/cas_generator.py index 511b893..26ff32a 100644 --- a/src/cas_parser/resources/cas_generator.py +++ b/src/cas_parser/resources/cas_generator.py @@ -7,7 +7,7 @@ import httpx from ..types import cas_generator_generate_cas_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -50,14 +50,14 @@ def generate_cas( from_date: str, password: str, to_date: str, - cas_authority: Literal["kfintech", "cams", "cdsl", "nsdl"] | NotGiven = NOT_GIVEN, - pan_no: str | NotGiven = NOT_GIVEN, + cas_authority: Literal["kfintech", "cams", "cdsl", "nsdl"] | Omit = omit, + pan_no: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CasGeneratorGenerateCasResponse: """ This endpoint generates CAS (Consolidated Account Statement) documents by @@ -133,14 +133,14 @@ async def generate_cas( from_date: str, password: str, to_date: str, - cas_authority: Literal["kfintech", "cams", "cdsl", "nsdl"] | NotGiven = NOT_GIVEN, - pan_no: str | NotGiven = NOT_GIVEN, + cas_authority: Literal["kfintech", "cams", "cdsl", "nsdl"] | Omit = omit, + pan_no: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CasGeneratorGenerateCasResponse: """ This endpoint generates CAS (Consolidated Account Statement) documents by diff --git a/src/cas_parser/resources/cas_parser.py b/src/cas_parser/resources/cas_parser.py index a64b7dd..e82b0e9 100644 --- a/src/cas_parser/resources/cas_parser.py +++ b/src/cas_parser/resources/cas_parser.py @@ -12,7 +12,7 @@ cas_parser_smart_parse_params, cas_parser_cams_kfintech_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -51,15 +51,15 @@ def with_streaming_response(self) -> CasParserResourceWithStreamingResponse: def cams_kfintech( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint specifically parses CAMS/KFintech CAS (Consolidated Account @@ -107,15 +107,15 @@ def cams_kfintech( def cdsl( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint specifically parses CDSL CAS (Consolidated Account Statement) PDF @@ -163,15 +163,15 @@ def cdsl( def nsdl( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint specifically parses NSDL CAS (Consolidated Account Statement) PDF @@ -219,15 +219,15 @@ def nsdl( def smart_parse( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint parses CAS (Consolidated Account Statement) PDF files from NSDL, @@ -297,15 +297,15 @@ def with_streaming_response(self) -> AsyncCasParserResourceWithStreamingResponse async def cams_kfintech( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint specifically parses CAMS/KFintech CAS (Consolidated Account @@ -353,15 +353,15 @@ async def cams_kfintech( async def cdsl( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint specifically parses CDSL CAS (Consolidated Account Statement) PDF @@ -409,15 +409,15 @@ async def cdsl( async def nsdl( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint specifically parses NSDL CAS (Consolidated Account Statement) PDF @@ -465,15 +465,15 @@ async def nsdl( async def smart_parse( self, *, - password: str | NotGiven = NOT_GIVEN, - pdf_file: str | NotGiven = NOT_GIVEN, - pdf_url: str | NotGiven = NOT_GIVEN, + password: str | Omit = omit, + pdf_file: str | Omit = omit, + pdf_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UnifiedResponse: """ This endpoint parses CAS (Consolidated Account Statement) PDF files from NSDL, diff --git a/src/cas_parser/types/unified_response.py b/src/cas_parser/types/unified_response.py index 7dc5439..4c17c58 100644 --- a/src/cas_parser/types/unified_response.py +++ b/src/cas_parser/types/unified_response.py @@ -18,6 +18,7 @@ "DematAccountHoldingsDematMutualFund", "DematAccountHoldingsEquity", "DematAccountHoldingsGovernmentSecurity", + "DematAccountLinkedHolder", "Insurance", "InsuranceLifeInsurancePolicy", "Investor", @@ -25,15 +26,21 @@ "MetaStatementPeriod", "MutualFund", "MutualFundAdditionalInfo", + "MutualFundLinkedHolder", "MutualFundScheme", "MutualFundSchemeAdditionalInfo", "MutualFundSchemeGain", "MutualFundSchemeTransaction", + "Np", + "NpFund", + "NpFundAdditionalInfo", + "NpLinkedHolder", "Summary", "SummaryAccounts", "SummaryAccountsDemat", "SummaryAccountsInsurance", "SummaryAccountsMutualFunds", + "SummaryAccountsNps", ] @@ -160,6 +167,14 @@ class DematAccountHoldings(BaseModel): government_securities: Optional[List[DematAccountHoldingsGovernmentSecurity]] = None +class DematAccountLinkedHolder(BaseModel): + name: Optional[str] = None + """Name of the account holder""" + + pan: Optional[str] = None + """PAN of the account holder""" + + class DematAccount(BaseModel): additional_info: Optional[DematAccountAdditionalInfo] = None """Additional information specific to the demat account type""" @@ -181,6 +196,9 @@ class DematAccount(BaseModel): holdings: Optional[DematAccountHoldings] = None + linked_holders: Optional[List[DematAccountLinkedHolder]] = None + """List of account holders linked to this demat account""" + value: Optional[float] = None """Total value of the demat account""" @@ -270,6 +288,14 @@ class MutualFundAdditionalInfo(BaseModel): """PAN KYC status""" +class MutualFundLinkedHolder(BaseModel): + name: Optional[str] = None + """Name of the account holder""" + + pan: Optional[str] = None + """PAN of the account holder""" + + class MutualFundSchemeAdditionalInfo(BaseModel): advisor: Optional[str] = None """Financial advisor name (CAMS/KFintech)""" @@ -370,6 +396,9 @@ class MutualFund(BaseModel): folio_number: Optional[str] = None """Folio number""" + linked_holders: Optional[List[MutualFundLinkedHolder]] = None + """List of account holders linked to this mutual fund folio""" + registrar: Optional[str] = None """Registrar and Transfer Agent name""" @@ -379,6 +408,61 @@ class MutualFund(BaseModel): """Total value of the folio""" +class NpFundAdditionalInfo(BaseModel): + manager: Optional[str] = None + """Fund manager name""" + + tier: Optional[Literal[1, 2]] = None + """NPS tier (Tier I or Tier II)""" + + +class NpFund(BaseModel): + additional_info: Optional[NpFundAdditionalInfo] = None + """Additional information specific to the NPS fund""" + + cost: Optional[float] = None + """Cost of investment""" + + name: Optional[str] = None + """Name of the NPS fund""" + + nav: Optional[float] = None + """Net Asset Value per unit""" + + units: Optional[float] = None + """Number of units held""" + + value: Optional[float] = None + """Current market value of the holding""" + + +class NpLinkedHolder(BaseModel): + name: Optional[str] = None + """Name of the account holder""" + + pan: Optional[str] = None + """PAN of the account holder""" + + +class Np(BaseModel): + additional_info: Optional[object] = None + """Additional information specific to the NPS account""" + + cra: Optional[str] = None + """Central Record Keeping Agency name""" + + funds: Optional[List[NpFund]] = None + + linked_holders: Optional[List[NpLinkedHolder]] = None + """List of account holders linked to this NPS account""" + + pran: Optional[str] = None + """Permanent Retirement Account Number (PRAN)""" + + value: Optional[float] = None + """Total value of the NPS account""" + + class SummaryAccountsDemat(BaseModel): count: Optional[int] = None """Number of demat accounts""" @@ -403,6 +487,14 @@ class SummaryAccountsMutualFunds(BaseModel): """Total value of mutual funds""" +class SummaryAccountsNps(BaseModel): + count: Optional[int] = None + """Number of NPS accounts""" + + total_value: Optional[float] = None + """Total value of NPS accounts""" + + class SummaryAccounts(BaseModel): demat: Optional[SummaryAccountsDemat] = None @@ -410,6 +502,8 @@ class SummaryAccounts(BaseModel): mutual_funds: Optional[SummaryAccountsMutualFunds] = None + nps: Optional[SummaryAccountsNps] = None + class Summary(BaseModel): accounts: Optional[SummaryAccounts] = None @@ -429,4 +523,7 @@ class UnifiedResponse(BaseModel): mutual_funds: Optional[List[MutualFund]] = None + nps: Optional[List[Np]] = None + """List of NPS accounts""" + summary: Optional[Summary] = None diff --git a/tests/test_transform.py b/tests/test_transform.py index ce97c84..451ddf6 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from cas_parser._types import NOT_GIVEN, Base64FileInput +from cas_parser._types import Base64FileInput, omit, not_given from cas_parser._utils import ( PropertyInfo, transform as _transform, @@ -450,4 +450,11 @@ async def test_transform_skipping(use_async: bool) -> None: @pytest.mark.asyncio async def test_strips_notgiven(use_async: bool) -> None: assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} - assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {}