diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbbfbc2..0b10752 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: '0.9.13' + version: '0.10.2' - name: Install dependencies run: uv sync --all-extras @@ -46,7 +46,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: '0.9.13' + version: '0.10.2' - name: Install dependencies run: uv sync --all-extras @@ -80,7 +80,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: '0.9.13' + version: '0.10.2' - name: Bootstrap run: ./scripts/bootstrap diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7deae33..59565e8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.6.0" + ".": "1.6.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7b570..dcd9180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 1.6.1 (2026-03-03) + +Full Changelog: [v1.6.0...v1.6.1](https://github.com/CASParser/cas-parser-python/compare/v1.6.0...v1.6.1) + +### Chores + +* **ci:** bump uv version ([8b38574](https://github.com/CASParser/cas-parser-python/commit/8b38574493387605ea62763e2dac86b90cb41697)) +* **internal:** add request options to SSE classes ([7488011](https://github.com/CASParser/cas-parser-python/commit/74880111923232fba22d9870f00aabf17cd7ef2f)) +* **internal:** codegen related update ([b9079a9](https://github.com/CASParser/cas-parser-python/commit/b9079a9473681d297cfdd4165e2aae413d784bfd)) +* **internal:** make `test_proxy_environment_variables` more resilient ([3608c6e](https://github.com/CASParser/cas-parser-python/commit/3608c6eddf68e65c153c4e186346b79859aab866)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([60ab278](https://github.com/CASParser/cas-parser-python/commit/60ab27826aeea8d2b9dc6ec19f8a94bc4108b180)) +* **internal:** refactor authentication internals ([deb70b3](https://github.com/CASParser/cas-parser-python/commit/deb70b3a007f6596260f115d964f69cd55b456c3)) + ## 1.6.0 (2026-02-23) Full Changelog: [v1.5.0...v1.6.0](https://github.com/CASParser/cas-parser-python/compare/v1.5.0...v1.6.0) diff --git a/pyproject.toml b/pyproject.toml index 85106e5..1e47163 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cas-parser-python" -version = "1.6.0" +version = "1.6.1" description = "The official Python library for the cas-parser API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/cas_parser/_base_client.py b/src/cas_parser/_base_client.py index 9937027..5b130cf 100644 --- a/src/cas_parser/_base_client.py +++ b/src/cas_parser/_base_client.py @@ -63,7 +63,7 @@ ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping from ._compat import PYDANTIC_V1, model_copy, model_dump -from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type +from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, BaseAPIResponse, @@ -432,9 +432,27 @@ def _make_status_error( ) -> _exceptions.APIStatusError: raise NotImplementedError() + def _auth_headers( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _auth_query( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _custom_auth( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> httpx.Auth | None: + return None + def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: custom_headers = options.headers or {} - headers_dict = _merge_mappings(self.default_headers, custom_headers) + headers_dict = _merge_mappings({**self._auth_headers(options.security), **self.default_headers}, custom_headers) self._validate_headers(headers_dict, custom_headers) # headers are case-insensitive while dictionaries are not. @@ -506,7 +524,7 @@ def _build_request( raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") headers = self._build_headers(options, retries_taken=retries_taken) - params = _merge_mappings(self.default_query, options.params) + params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params) content_type = headers.get("Content-Type") files = options.files @@ -671,7 +689,6 @@ def default_headers(self) -> dict[str, str | Omit]: "Content-Type": "application/json", "User-Agent": self.user_agent, **self.platform_headers(), - **self.auth_headers, **self._custom_headers, } @@ -990,8 +1007,9 @@ def request( self._prepare_request(request) kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + custom_auth = self._custom_auth(options.security) + if custom_auth is not None: + kwargs["auth"] = custom_auth if options.follow_redirects is not None: kwargs["follow_redirects"] = options.follow_redirects @@ -1952,6 +1970,7 @@ def make_request_options( idempotency_key: str | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, post_parser: PostParser | NotGiven = not_given, + security: SecurityOptions | None = None, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} @@ -1977,6 +1996,9 @@ def make_request_options( # internal options["post_parser"] = post_parser # type: ignore + if security is not None: + options["security"] = security + return options diff --git a/src/cas_parser/_client.py b/src/cas_parser/_client.py index ebe71c1..1f11392 100644 --- a/src/cas_parser/_client.py +++ b/src/cas_parser/_client.py @@ -21,6 +21,7 @@ ) from ._utils import is_given, get_async_library from ._compat import cached_property +from ._models import SecurityOptions from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, CasParserError @@ -127,72 +128,136 @@ def __init__( @cached_property def credits(self) -> CreditsResource: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.credits import CreditsResource return CreditsResource(self) @cached_property def logs(self) -> LogsResource: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.logs import LogsResource return LogsResource(self) @cached_property def access_token(self) -> AccessTokenResource: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.access_token import AccessTokenResource return AccessTokenResource(self) @cached_property def verify_token(self) -> VerifyTokenResource: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.verify_token import VerifyTokenResource return VerifyTokenResource(self) @cached_property def cams_kfintech(self) -> CamsKfintechResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cams_kfintech import CamsKfintechResource return CamsKfintechResource(self) @cached_property def cdsl(self) -> CdslResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cdsl import CdslResource return CdslResource(self) @cached_property def contract_note(self) -> ContractNoteResource: + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ from .resources.contract_note import ContractNoteResource return ContractNoteResource(self) @cached_property def inbox(self) -> InboxResource: + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ from .resources.inbox import InboxResource return InboxResource(self) @cached_property def kfintech(self) -> KfintechResource: + """Endpoints for generating new CAS documents via email mailback (KFintech).""" from .resources.kfintech import KfintechResource return KfintechResource(self) @cached_property def nsdl(self) -> NsdlResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.nsdl import NsdlResource return NsdlResource(self) @cached_property def smart(self) -> SmartResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.smart import SmartResource return SmartResource(self) @cached_property def inbound_email(self) -> InboundEmailResource: + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ from .resources.inbound_email import InboundEmailResource return InboundEmailResource(self) @@ -210,9 +275,14 @@ def with_streaming_response(self) -> CasParserWithStreamedResponse: def qs(self) -> Querystring: return Querystring(array_format="comma") - @property @override - def auth_headers(self) -> dict[str, str]: + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: + return { + **(self._api_key_auth if security.get("api_key_auth", False) else {}), + } + + @property + def _api_key_auth(self) -> dict[str, str]: api_key = self.api_key return {"x-api-key": api_key} @@ -367,72 +437,136 @@ def __init__( @cached_property def credits(self) -> AsyncCreditsResource: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.credits import AsyncCreditsResource return AsyncCreditsResource(self) @cached_property def logs(self) -> AsyncLogsResource: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.logs import AsyncLogsResource return AsyncLogsResource(self) @cached_property def access_token(self) -> AsyncAccessTokenResource: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.access_token import AsyncAccessTokenResource return AsyncAccessTokenResource(self) @cached_property def verify_token(self) -> AsyncVerifyTokenResource: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.verify_token import AsyncVerifyTokenResource return AsyncVerifyTokenResource(self) @cached_property def cams_kfintech(self) -> AsyncCamsKfintechResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cams_kfintech import AsyncCamsKfintechResource return AsyncCamsKfintechResource(self) @cached_property def cdsl(self) -> AsyncCdslResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cdsl import AsyncCdslResource return AsyncCdslResource(self) @cached_property def contract_note(self) -> AsyncContractNoteResource: + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ from .resources.contract_note import AsyncContractNoteResource return AsyncContractNoteResource(self) @cached_property def inbox(self) -> AsyncInboxResource: + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ from .resources.inbox import AsyncInboxResource return AsyncInboxResource(self) @cached_property def kfintech(self) -> AsyncKfintechResource: + """Endpoints for generating new CAS documents via email mailback (KFintech).""" from .resources.kfintech import AsyncKfintechResource return AsyncKfintechResource(self) @cached_property def nsdl(self) -> AsyncNsdlResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.nsdl import AsyncNsdlResource return AsyncNsdlResource(self) @cached_property def smart(self) -> AsyncSmartResource: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.smart import AsyncSmartResource return AsyncSmartResource(self) @cached_property def inbound_email(self) -> AsyncInboundEmailResource: + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ from .resources.inbound_email import AsyncInboundEmailResource return AsyncInboundEmailResource(self) @@ -450,9 +584,14 @@ def with_streaming_response(self) -> AsyncCasParserWithStreamedResponse: def qs(self) -> Querystring: return Querystring(array_format="comma") - @property @override - def auth_headers(self) -> dict[str, str]: + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: + return { + **(self._api_key_auth if security.get("api_key_auth", False) else {}), + } + + @property + def _api_key_auth(self) -> dict[str, str]: api_key = self.api_key return {"x-api-key": api_key} @@ -558,72 +697,136 @@ def __init__(self, client: CasParser) -> None: @cached_property def credits(self) -> credits.CreditsResourceWithRawResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.credits import CreditsResourceWithRawResponse return CreditsResourceWithRawResponse(self._client.credits) @cached_property def logs(self) -> logs.LogsResourceWithRawResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.logs import LogsResourceWithRawResponse return LogsResourceWithRawResponse(self._client.logs) @cached_property def access_token(self) -> access_token.AccessTokenResourceWithRawResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.access_token import AccessTokenResourceWithRawResponse return AccessTokenResourceWithRawResponse(self._client.access_token) @cached_property def verify_token(self) -> verify_token.VerifyTokenResourceWithRawResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.verify_token import VerifyTokenResourceWithRawResponse return VerifyTokenResourceWithRawResponse(self._client.verify_token) @cached_property def cams_kfintech(self) -> cams_kfintech.CamsKfintechResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cams_kfintech import CamsKfintechResourceWithRawResponse return CamsKfintechResourceWithRawResponse(self._client.cams_kfintech) @cached_property def cdsl(self) -> cdsl.CdslResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cdsl import CdslResourceWithRawResponse return CdslResourceWithRawResponse(self._client.cdsl) @cached_property def contract_note(self) -> contract_note.ContractNoteResourceWithRawResponse: + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ from .resources.contract_note import ContractNoteResourceWithRawResponse return ContractNoteResourceWithRawResponse(self._client.contract_note) @cached_property def inbox(self) -> inbox.InboxResourceWithRawResponse: + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ from .resources.inbox import InboxResourceWithRawResponse return InboxResourceWithRawResponse(self._client.inbox) @cached_property def kfintech(self) -> kfintech.KfintechResourceWithRawResponse: + """Endpoints for generating new CAS documents via email mailback (KFintech).""" from .resources.kfintech import KfintechResourceWithRawResponse return KfintechResourceWithRawResponse(self._client.kfintech) @cached_property def nsdl(self) -> nsdl.NsdlResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.nsdl import NsdlResourceWithRawResponse return NsdlResourceWithRawResponse(self._client.nsdl) @cached_property def smart(self) -> smart.SmartResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.smart import SmartResourceWithRawResponse return SmartResourceWithRawResponse(self._client.smart) @cached_property def inbound_email(self) -> inbound_email.InboundEmailResourceWithRawResponse: + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ from .resources.inbound_email import InboundEmailResourceWithRawResponse return InboundEmailResourceWithRawResponse(self._client.inbound_email) @@ -637,72 +840,136 @@ def __init__(self, client: AsyncCasParser) -> None: @cached_property def credits(self) -> credits.AsyncCreditsResourceWithRawResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.credits import AsyncCreditsResourceWithRawResponse return AsyncCreditsResourceWithRawResponse(self._client.credits) @cached_property def logs(self) -> logs.AsyncLogsResourceWithRawResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.logs import AsyncLogsResourceWithRawResponse return AsyncLogsResourceWithRawResponse(self._client.logs) @cached_property def access_token(self) -> access_token.AsyncAccessTokenResourceWithRawResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.access_token import AsyncAccessTokenResourceWithRawResponse return AsyncAccessTokenResourceWithRawResponse(self._client.access_token) @cached_property def verify_token(self) -> verify_token.AsyncVerifyTokenResourceWithRawResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.verify_token import AsyncVerifyTokenResourceWithRawResponse return AsyncVerifyTokenResourceWithRawResponse(self._client.verify_token) @cached_property def cams_kfintech(self) -> cams_kfintech.AsyncCamsKfintechResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cams_kfintech import AsyncCamsKfintechResourceWithRawResponse return AsyncCamsKfintechResourceWithRawResponse(self._client.cams_kfintech) @cached_property def cdsl(self) -> cdsl.AsyncCdslResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cdsl import AsyncCdslResourceWithRawResponse return AsyncCdslResourceWithRawResponse(self._client.cdsl) @cached_property def contract_note(self) -> contract_note.AsyncContractNoteResourceWithRawResponse: + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ from .resources.contract_note import AsyncContractNoteResourceWithRawResponse return AsyncContractNoteResourceWithRawResponse(self._client.contract_note) @cached_property def inbox(self) -> inbox.AsyncInboxResourceWithRawResponse: + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ from .resources.inbox import AsyncInboxResourceWithRawResponse return AsyncInboxResourceWithRawResponse(self._client.inbox) @cached_property def kfintech(self) -> kfintech.AsyncKfintechResourceWithRawResponse: + """Endpoints for generating new CAS documents via email mailback (KFintech).""" from .resources.kfintech import AsyncKfintechResourceWithRawResponse return AsyncKfintechResourceWithRawResponse(self._client.kfintech) @cached_property def nsdl(self) -> nsdl.AsyncNsdlResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.nsdl import AsyncNsdlResourceWithRawResponse return AsyncNsdlResourceWithRawResponse(self._client.nsdl) @cached_property def smart(self) -> smart.AsyncSmartResourceWithRawResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.smart import AsyncSmartResourceWithRawResponse return AsyncSmartResourceWithRawResponse(self._client.smart) @cached_property def inbound_email(self) -> inbound_email.AsyncInboundEmailResourceWithRawResponse: + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ from .resources.inbound_email import AsyncInboundEmailResourceWithRawResponse return AsyncInboundEmailResourceWithRawResponse(self._client.inbound_email) @@ -716,72 +983,136 @@ def __init__(self, client: CasParser) -> None: @cached_property def credits(self) -> credits.CreditsResourceWithStreamingResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.credits import CreditsResourceWithStreamingResponse return CreditsResourceWithStreamingResponse(self._client.credits) @cached_property def logs(self) -> logs.LogsResourceWithStreamingResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.logs import LogsResourceWithStreamingResponse return LogsResourceWithStreamingResponse(self._client.logs) @cached_property def access_token(self) -> access_token.AccessTokenResourceWithStreamingResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.access_token import AccessTokenResourceWithStreamingResponse return AccessTokenResourceWithStreamingResponse(self._client.access_token) @cached_property def verify_token(self) -> verify_token.VerifyTokenResourceWithStreamingResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.verify_token import VerifyTokenResourceWithStreamingResponse return VerifyTokenResourceWithStreamingResponse(self._client.verify_token) @cached_property def cams_kfintech(self) -> cams_kfintech.CamsKfintechResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cams_kfintech import CamsKfintechResourceWithStreamingResponse return CamsKfintechResourceWithStreamingResponse(self._client.cams_kfintech) @cached_property def cdsl(self) -> cdsl.CdslResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cdsl import CdslResourceWithStreamingResponse return CdslResourceWithStreamingResponse(self._client.cdsl) @cached_property def contract_note(self) -> contract_note.ContractNoteResourceWithStreamingResponse: + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ from .resources.contract_note import ContractNoteResourceWithStreamingResponse return ContractNoteResourceWithStreamingResponse(self._client.contract_note) @cached_property def inbox(self) -> inbox.InboxResourceWithStreamingResponse: + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ from .resources.inbox import InboxResourceWithStreamingResponse return InboxResourceWithStreamingResponse(self._client.inbox) @cached_property def kfintech(self) -> kfintech.KfintechResourceWithStreamingResponse: + """Endpoints for generating new CAS documents via email mailback (KFintech).""" from .resources.kfintech import KfintechResourceWithStreamingResponse return KfintechResourceWithStreamingResponse(self._client.kfintech) @cached_property def nsdl(self) -> nsdl.NsdlResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.nsdl import NsdlResourceWithStreamingResponse return NsdlResourceWithStreamingResponse(self._client.nsdl) @cached_property def smart(self) -> smart.SmartResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.smart import SmartResourceWithStreamingResponse return SmartResourceWithStreamingResponse(self._client.smart) @cached_property def inbound_email(self) -> inbound_email.InboundEmailResourceWithStreamingResponse: + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ from .resources.inbound_email import InboundEmailResourceWithStreamingResponse return InboundEmailResourceWithStreamingResponse(self._client.inbound_email) @@ -795,72 +1126,136 @@ def __init__(self, client: AsyncCasParser) -> None: @cached_property def credits(self) -> credits.AsyncCreditsResourceWithStreamingResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.credits import AsyncCreditsResourceWithStreamingResponse return AsyncCreditsResourceWithStreamingResponse(self._client.credits) @cached_property def logs(self) -> logs.AsyncLogsResourceWithStreamingResponse: + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ from .resources.logs import AsyncLogsResourceWithStreamingResponse return AsyncLogsResourceWithStreamingResponse(self._client.logs) @cached_property def access_token(self) -> access_token.AsyncAccessTokenResourceWithStreamingResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.access_token import AsyncAccessTokenResourceWithStreamingResponse return AsyncAccessTokenResourceWithStreamingResponse(self._client.access_token) @cached_property def verify_token(self) -> verify_token.AsyncVerifyTokenResourceWithStreamingResponse: + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ from .resources.verify_token import AsyncVerifyTokenResourceWithStreamingResponse return AsyncVerifyTokenResourceWithStreamingResponse(self._client.verify_token) @cached_property def cams_kfintech(self) -> cams_kfintech.AsyncCamsKfintechResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cams_kfintech import AsyncCamsKfintechResourceWithStreamingResponse return AsyncCamsKfintechResourceWithStreamingResponse(self._client.cams_kfintech) @cached_property def cdsl(self) -> cdsl.AsyncCdslResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.cdsl import AsyncCdslResourceWithStreamingResponse return AsyncCdslResourceWithStreamingResponse(self._client.cdsl) @cached_property def contract_note(self) -> contract_note.AsyncContractNoteResourceWithStreamingResponse: + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ from .resources.contract_note import AsyncContractNoteResourceWithStreamingResponse return AsyncContractNoteResourceWithStreamingResponse(self._client.contract_note) @cached_property def inbox(self) -> inbox.AsyncInboxResourceWithStreamingResponse: + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ from .resources.inbox import AsyncInboxResourceWithStreamingResponse return AsyncInboxResourceWithStreamingResponse(self._client.inbox) @cached_property def kfintech(self) -> kfintech.AsyncKfintechResourceWithStreamingResponse: + """Endpoints for generating new CAS documents via email mailback (KFintech).""" from .resources.kfintech import AsyncKfintechResourceWithStreamingResponse return AsyncKfintechResourceWithStreamingResponse(self._client.kfintech) @cached_property def nsdl(self) -> nsdl.AsyncNsdlResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.nsdl import AsyncNsdlResourceWithStreamingResponse return AsyncNsdlResourceWithStreamingResponse(self._client.nsdl) @cached_property def smart(self) -> smart.AsyncSmartResourceWithStreamingResponse: + """Endpoints for parsing CAS PDF files from different sources.""" from .resources.smart import AsyncSmartResourceWithStreamingResponse return AsyncSmartResourceWithStreamingResponse(self._client.smart) @cached_property def inbound_email(self) -> inbound_email.AsyncInboundEmailResourceWithStreamingResponse: + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ from .resources.inbound_email import AsyncInboundEmailResourceWithStreamingResponse return AsyncInboundEmailResourceWithStreamingResponse(self._client.inbound_email) diff --git a/src/cas_parser/_models.py b/src/cas_parser/_models.py index 29070e0..6df43eb 100644 --- a/src/cas_parser/_models.py +++ b/src/cas_parser/_models.py @@ -791,6 +791,10 @@ def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: return RootModel[type_] # type: ignore +class SecurityOptions(TypedDict, total=False): + api_key_auth: bool + + class FinalRequestOptionsInput(TypedDict, total=False): method: Required[str] url: Required[str] @@ -804,6 +808,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): json_data: Body extra_json: AnyMapping follow_redirects: bool + security: SecurityOptions @final @@ -818,6 +823,7 @@ class FinalRequestOptions(pydantic.BaseModel): idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() follow_redirects: Union[bool, None] = None + security: SecurityOptions = {"api_key_auth": True} content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override diff --git a/src/cas_parser/_response.py b/src/cas_parser/_response.py index a67d3f1..66c0f1a 100644 --- a/src/cas_parser/_response.py +++ b/src/cas_parser/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/cas_parser/_streaming.py b/src/cas_parser/_streaming.py index 00e2105..121e6be 100644 --- a/src/cas_parser/_streaming.py +++ b/src/cas_parser/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import CasParser, AsyncCasParser + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: CasParser, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncCasParser, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() diff --git a/src/cas_parser/_types.py b/src/cas_parser/_types.py index 3f7802c..f3d52b8 100644 --- a/src/cas_parser/_types.py +++ b/src/cas_parser/_types.py @@ -36,7 +36,7 @@ from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport if TYPE_CHECKING: - from ._models import BaseModel + from ._models import BaseModel, SecurityOptions from ._response import APIResponse, AsyncAPIResponse Transport = BaseTransport @@ -121,6 +121,7 @@ class RequestOptions(TypedDict, total=False): extra_json: AnyMapping idempotency_key: str follow_redirects: bool + security: SecurityOptions # Sentinel class used until PEP 0661 is accepted diff --git a/src/cas_parser/_version.py b/src/cas_parser/_version.py index 1d9ca9a..a11f5b5 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.6.0" # x-release-please-version +__version__ = "1.6.1" # x-release-please-version diff --git a/src/cas_parser/resources/access_token.py b/src/cas_parser/resources/access_token.py index bfda93b..fcf841c 100644 --- a/src/cas_parser/resources/access_token.py +++ b/src/cas_parser/resources/access_token.py @@ -22,6 +22,12 @@ class AccessTokenResource(SyncAPIResource): + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ + @cached_property def with_raw_response(self) -> AccessTokenResourceWithRawResponse: """ @@ -91,6 +97,12 @@ def create( class AsyncAccessTokenResource(AsyncAPIResource): + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ + @cached_property def with_raw_response(self) -> AsyncAccessTokenResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/cams_kfintech.py b/src/cas_parser/resources/cams_kfintech.py index 04ef754..fb32699 100644 --- a/src/cas_parser/resources/cams_kfintech.py +++ b/src/cas_parser/resources/cams_kfintech.py @@ -24,6 +24,8 @@ class CamsKfintechResource(SyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def with_raw_response(self) -> CamsKfintechResourceWithRawResponse: """ @@ -101,6 +103,8 @@ def parse( class AsyncCamsKfintechResource(AsyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def with_raw_response(self) -> AsyncCamsKfintechResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/cdsl/cdsl.py b/src/cas_parser/resources/cdsl/cdsl.py index d3b39ff..d0c69b9 100644 --- a/src/cas_parser/resources/cdsl/cdsl.py +++ b/src/cas_parser/resources/cdsl/cdsl.py @@ -32,8 +32,14 @@ class CdslResource(SyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def fetch(self) -> FetchResource: + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ return FetchResource(self._client) @cached_property @@ -113,8 +119,14 @@ def parse_pdf( class AsyncCdslResource(AsyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def fetch(self) -> AsyncFetchResource: + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ return AsyncFetchResource(self._client) @cached_property @@ -203,6 +215,10 @@ def __init__(self, cdsl: CdslResource) -> None: @cached_property def fetch(self) -> FetchResourceWithRawResponse: + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ return FetchResourceWithRawResponse(self._cdsl.fetch) @@ -216,6 +232,10 @@ def __init__(self, cdsl: AsyncCdslResource) -> None: @cached_property def fetch(self) -> AsyncFetchResourceWithRawResponse: + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ return AsyncFetchResourceWithRawResponse(self._cdsl.fetch) @@ -229,6 +249,10 @@ def __init__(self, cdsl: CdslResource) -> None: @cached_property def fetch(self) -> FetchResourceWithStreamingResponse: + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ return FetchResourceWithStreamingResponse(self._cdsl.fetch) @@ -242,4 +266,8 @@ def __init__(self, cdsl: AsyncCdslResource) -> None: @cached_property def fetch(self) -> AsyncFetchResourceWithStreamingResponse: + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ return AsyncFetchResourceWithStreamingResponse(self._cdsl.fetch) diff --git a/src/cas_parser/resources/cdsl/fetch.py b/src/cas_parser/resources/cdsl/fetch.py index abbb6a1..191c3a7 100644 --- a/src/cas_parser/resources/cdsl/fetch.py +++ b/src/cas_parser/resources/cdsl/fetch.py @@ -23,6 +23,11 @@ class FetchResource(SyncAPIResource): + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ + @cached_property def with_raw_response(self) -> FetchResourceWithRawResponse: """ @@ -149,6 +154,11 @@ def verify_otp( class AsyncFetchResource(AsyncAPIResource): + """ + Endpoints for fetching CAS documents with instant download. + Currently supports CDSL via OTP authentication. + """ + @cached_property def with_raw_response(self) -> AsyncFetchResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/contract_note.py b/src/cas_parser/resources/contract_note.py index 5136c68..7133be7 100644 --- a/src/cas_parser/resources/contract_note.py +++ b/src/cas_parser/resources/contract_note.py @@ -25,6 +25,10 @@ class ContractNoteResource(SyncAPIResource): + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ + @cached_property def with_raw_response(self) -> ContractNoteResourceWithRawResponse: """ @@ -132,6 +136,10 @@ def parse( class AsyncContractNoteResource(AsyncAPIResource): + """ + Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. + """ + @cached_property def with_raw_response(self) -> AsyncContractNoteResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/credits.py b/src/cas_parser/resources/credits.py index 264693d..0553441 100644 --- a/src/cas_parser/resources/credits.py +++ b/src/cas_parser/resources/credits.py @@ -20,6 +20,11 @@ class CreditsResource(SyncAPIResource): + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ + @cached_property def with_raw_response(self) -> CreditsResourceWithRawResponse: """ @@ -70,6 +75,11 @@ def check( class AsyncCreditsResource(AsyncAPIResource): + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ + @cached_property def with_raw_response(self) -> AsyncCreditsResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/inbound_email.py b/src/cas_parser/resources/inbound_email.py index e20ae7b..3e45f5d 100644 --- a/src/cas_parser/resources/inbound_email.py +++ b/src/cas_parser/resources/inbound_email.py @@ -28,6 +28,29 @@ class InboundEmailResource(SyncAPIResource): + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ + @cached_property def with_raw_response(self) -> InboundEmailResourceWithRawResponse: """ @@ -260,6 +283,29 @@ def delete( class AsyncInboundEmailResource(AsyncAPIResource): + """ + Create dedicated inbound email addresses for investors to forward their CAS statements. + + **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. + + **How it works:** + 1. Call `POST /v4/inbound-email` to create a unique inbound email address + 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + 4. Your webhook receives email metadata + attachment download URLs + + **Sender Validation:** + - Only emails from verified CAS authorities are processed: + - CDSL: `eCAS@cdslstatement.com` + - NSDL: `NSDL-CAS@nsdl.co.in` + - CAMS: `donotreply@camsonline.com` + - KFintech: `samfS@kfintech.com` + - Emails failing SPF/DKIM/DMARC are rejected + - Forwarded emails must contain the original sender in headers + + **Billing:** 0.2 credits per successfully processed valid email + """ + @cached_property def with_raw_response(self) -> AsyncInboundEmailResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/inbox.py b/src/cas_parser/resources/inbox.py index dd8e8c9..7692744 100644 --- a/src/cas_parser/resources/inbox.py +++ b/src/cas_parser/resources/inbox.py @@ -29,6 +29,23 @@ class InboxResource(SyncAPIResource): + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ + @cached_property def with_raw_response(self) -> InboxResourceWithRawResponse: """ @@ -246,6 +263,23 @@ def list_cas_files( class AsyncInboxResource(AsyncAPIResource): + """Endpoints for importing CAS files directly from user email inboxes. + + **Supported Providers:** Gmail (more coming soon) + + **How it works:** + 1. Call `POST /v4/inbox/connect` to get an OAuth URL + 2. Redirect user to the OAuth URL for consent + 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + + **Security:** + - Read-only access (we cannot send emails) + - Tokens are encrypted with server-side secret + - User can revoke access anytime via `/v4/inbox/disconnect` + """ + @cached_property def with_raw_response(self) -> AsyncInboxResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/kfintech.py b/src/cas_parser/resources/kfintech.py index 32077c5..358f415 100644 --- a/src/cas_parser/resources/kfintech.py +++ b/src/cas_parser/resources/kfintech.py @@ -22,6 +22,8 @@ class KfintechResource(SyncAPIResource): + """Endpoints for generating new CAS documents via email mailback (KFintech).""" + @cached_property def with_raw_response(self) -> KfintechResourceWithRawResponse: """ @@ -103,6 +105,8 @@ def generate_cas( class AsyncKfintechResource(AsyncAPIResource): + """Endpoints for generating new CAS documents via email mailback (KFintech).""" + @cached_property def with_raw_response(self) -> AsyncKfintechResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/logs.py b/src/cas_parser/resources/logs.py index 45ba056..625baee 100644 --- a/src/cas_parser/resources/logs.py +++ b/src/cas_parser/resources/logs.py @@ -26,6 +26,11 @@ class LogsResource(SyncAPIResource): + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ + @cached_property def with_raw_response(self) -> LogsResourceWithRawResponse: """ @@ -147,6 +152,11 @@ def get_summary( class AsyncLogsResource(AsyncAPIResource): + """ + Endpoints for checking API quota and credits usage. + These endpoints help you monitor your API usage and remaining quota. + """ + @cached_property def with_raw_response(self) -> AsyncLogsResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/nsdl.py b/src/cas_parser/resources/nsdl.py index 4911e54..4312757 100644 --- a/src/cas_parser/resources/nsdl.py +++ b/src/cas_parser/resources/nsdl.py @@ -24,6 +24,8 @@ class NsdlResource(SyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def with_raw_response(self) -> NsdlResourceWithRawResponse: """ @@ -101,6 +103,8 @@ def parse( class AsyncNsdlResource(AsyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def with_raw_response(self) -> AsyncNsdlResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/smart.py b/src/cas_parser/resources/smart.py index 41a4b6f..0d85213 100644 --- a/src/cas_parser/resources/smart.py +++ b/src/cas_parser/resources/smart.py @@ -24,6 +24,8 @@ class SmartResource(SyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def with_raw_response(self) -> SmartResourceWithRawResponse: """ @@ -102,6 +104,8 @@ def parse_cas_pdf( class AsyncSmartResource(AsyncAPIResource): + """Endpoints for parsing CAS PDF files from different sources.""" + @cached_property def with_raw_response(self) -> AsyncSmartResourceWithRawResponse: """ diff --git a/src/cas_parser/resources/verify_token.py b/src/cas_parser/resources/verify_token.py index 06981d4..2247e4c 100644 --- a/src/cas_parser/resources/verify_token.py +++ b/src/cas_parser/resources/verify_token.py @@ -20,6 +20,12 @@ class VerifyTokenResource(SyncAPIResource): + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ + @cached_property def with_raw_response(self) -> VerifyTokenResourceWithRawResponse: """ @@ -64,6 +70,12 @@ def verify( class AsyncVerifyTokenResource(AsyncAPIResource): + """ + Endpoints for managing access tokens for the Portfolio Connect SDK. + Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + Access tokens can be used in place of API keys on all v4 endpoints. + """ + @cached_property def with_raw_response(self) -> AsyncVerifyTokenResourceWithRawResponse: """ diff --git a/tests/test_client.py b/tests/test_client.py index ec19747..9e2377f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -947,6 +947,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1853,6 +1861,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient()