示例#1
0
    def request_tax1099(
        self,
        password: str,
        *taxyears: str,
        acctnum: str = None,
        recid: str = None,
        dryrun: bool = False,
        verify_ssl: bool = True,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Request US federal income tax form 1099 (TAX1099RQ)
        """
        logger.info("Creating tax 1099 request")
        signon = self.signon(password)

        rq = TAX1099RQ(*taxyears, recid=recid or None)
        msgs = TAX1099MSGSRQV1(TAX1099TRNRQ(trnuid=self.uuid, tax1099rq=rq))

        logger.debug(f"Wrapped tax 1099 request messages: {msgs}")

        ofx = OFX(signonmsgsrqv1=signon, tax1099msgsrqv1=msgs)
        return self.download(ofx,
                             dryrun=dryrun,
                             verify_ssl=verify_ssl,
                             timeout=timeout)
示例#2
0
    def request_profile(
        self,
        user=None,
        password=None,
        dryrun=False,
        prettyprint=False,
        close_elements=True,
    ):
        """
        Package and send OFX profile requests (PROFRQ).
        """

        dtprofup = datetime.datetime(1990, 1, 1, tzinfo=UTC)
        profrq = PROFRQ(clientrouting="NONE", dtprofup=dtprofup)
        trnuid = self.uuid
        proftrnrq = PROFTRNRQ(trnuid=trnuid, profrq=profrq)
        msgs = PROFMSGSRQV1(proftrnrq)

        user = user or "{:0<32}".format("anonymous")
        password = password or "{:0<32}".format("anonymous")
        signonmsgs = self.signon(user, password)

        ofx = OFX(signonmsgsrqv1=signonmsgs, profmsgsrqv1=msgs)
        return self.download(
            ofx, dryrun=dryrun, prettyprint=prettyprint, close_elements=close_elements
        )
示例#3
0
    def request_accounts(
        self,
        password: str,
        dtacctup: datetime.datetime,
        dryrun: bool = False,
        version: Optional[int] = None,
        verify_ssl: bool = True,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Package and send OFX account info requests (ACCTINFORQ)
        """
        logger.info("Creating account info request")
        signon = self.signon(password)

        acctinforq = ACCTINFORQ(dtacctup=dtacctup)
        acctinfotrnrq = ACCTINFOTRNRQ(trnuid=self.uuid, acctinforq=acctinforq)
        msgs = SIGNUPMSGSRQV1(acctinfotrnrq)

        logger.debug(f"Wrapped account info request messages: {msgs}")

        ofx = OFX(signonmsgsrqv1=signon, signupmsgsrqv1=msgs)
        return self.download(ofx,
                             dryrun=dryrun,
                             verify_ssl=verify_ssl,
                             timeout=timeout)
示例#4
0
    def serialize(self,
                  ofx: OFX,
                  version: Optional[int] = None,
                  prettyprint: Optional[bool] = None,
                  close_elements: Optional[bool] = None,
                  ) -> bytes:
        if version is None:
            version = self.version
        if prettyprint is None:
            prettyprint = self.prettyprint
        if close_elements is None:
            close_elements = self.close_elements
        header = bytes(str(make_header(version=version, newfileuid=self.uuid)),
                       "utf_8")

        tree = ofx.to_etree()
        if prettyprint:
            utils.indent(tree)

        # Some servers choke on OFXv1 requests including ending tags for
        # elements (which are optional per the spec).
        if close_elements is False:
            if version >= 200:
                msg = "OFX version {} requires ending tags for elements"
                raise ValueError(msg.format(version))
            body = utils.tostring_unclosed_elements(tree)
        else:
            # ``method="html"`` skips the initial XML declaration
            body = ET.tostring(tree, encoding="utf_8", method="html")

        return header + body
示例#5
0
    def request_statements(
        self,
        password: str,
        *requests: RequestParam,
        dryrun: bool = False,
        verify_ssl: bool = True,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Package and send OFX statement requests
        (STMTRQ/CCSTMTRQ/INVSTMTRQ/STMTENDRQ/CCSTMTENDRQ).
        """
        logger.info(f"Creating statement requests for {requests}")
        # Group requests by type and pass to the appropriate *TRNRQ handler
        # function (see singledispatch setup below).
        #
        # *StmtRq/*StmtEndRqs (namedtuples) don't have rich comparison methods;
        # can't sort by class. As a proxy, we sort by class name, even though
        # we actually group by request class so we can use it when iterating
        # over groupby().
        stmtSortKey = attrgetter("__class__.__name__")
        stmtGroupKey = attrgetter("__class__")
        trnrqs = [
            wrap_stmtrq(cls(), rqs, self)
            for cls, rqs in itertools.groupby(
                sorted(requests, key=stmtSortKey), key=stmtGroupKey
            )
        ]

        # trnrqs is a pair of (models.*MSGSRQV1, [*TRNRQ])
        # Can't sort *MSGSRQV1 by class, either, so we use the same trick
        # of sorting by class name and grouping by class.
        def trnSortKey(pair):
            return pair[0].__name__

        trnGroupKey = itemgetter(0)
        trnrqs.sort(key=trnSortKey)

        # N.B. we need to annotate first arg as typing.Type here to indicate that
        # we're passing in a class not an instance.
        def msg_args(
            msgcls: Union[
                Type[BANKMSGSRQV1], Type[CREDITCARDMSGSRQV1], Type[INVSTMTMSGSRQV1]
            ],
            trnrqs: Iterator[Request],
        ) -> Tuple[str, Message]:
            trnrqs_ = list(itertools.chain.from_iterable(t[1] for t in trnrqs))
            attr_name = msgcls.__name__.lower()
            return (attr_name, msgcls(*trnrqs_))

        msgs = dict(
            msg_args(msgcls, _trnrqs)
            for msgcls, _trnrqs in itertools.groupby(trnrqs, key=trnGroupKey)
        )
        logger.debug(f"Wrapped statement request messages: {msgs}")

        signon = self.signon(password)
        ofx = OFX(signonmsgsrqv1=signon, **msgs)
        return self.download(ofx, dryrun=dryrun, verify_ssl=verify_ssl, timeout=timeout)
    def serialize(
        self,
        ofx: OFX,
        version: Optional[int] = None,
        oldfileuid: Optional[str] = None,
        newfileuid: Optional[str] = None,
        prettyprint: Optional[bool] = None,
        close_elements: Optional[bool] = None,
    ) -> bytes:
        """
        Transform a ``models.OFX`` instance into bytestring representation
        with OFX header prepended.

        N.B. ``version`` / ``prettyprint`` / ``close_elements`` kwargs are
        basically hacks for ``scripts.ofxget.scan_profile()``; ordinarily you
        should initialize the ``OFXClient`` with the proper version# and
        formatting parameters, rather than overriding the client config here.

        Optional kwargs:
            ``version`` - OFX version to report in header
            ``oldfileuid`` - OLDFILEUID to report in header
            ``newfileuid`` - NEWFILEUID to report in header
            ``prettyprint`` - add newlines between tags and indentation
            ``close_elements`` - add markup closing tags to leaf elements
        """
        if version is None:
            version = self.version
        if prettyprint is None:
            prettyprint = self.prettyprint
        if close_elements is None:
            close_elements = self.close_elements
        header = bytes(
            str(
                make_header(
                    version=version, oldfileuid=oldfileuid, newfileuid=newfileuid
                )
            ),
            "utf_8",
        )

        tree = ofx.to_etree()
        if prettyprint:
            utils.indent(tree)

        # Some servers choke on OFXv1 requests including ending tags for
        # elements (which are optional per the spec).
        if close_elements is False:
            if version >= 200:
                raise ValueError(
                    f"OFX version {version} requires ending tags for elements"
                )
            body = utils.tostring_unclosed_elements(tree)
        else:
            # ``method="html"`` skips the initial XML declaration
            body = ET.tostring(tree, encoding="utf_8", method="html")

        return header + body
示例#7
0
    def request_end_statements(self,
                               user,
                               password,
                               *requests,
                               language=None,
                               clientuid=None,
                               appid=None,
                               appver=None,
                               version=None,
                               dryrun=False,
                               prettyprint=False,
                               close_elements=True,
                               timeout=None):
        """
        Package and send OFX end statement requests (STMTENDRQ, CCSTMTENDRQ).

        Input *requests are instances of the corresponding namedtuples
        (StmtEndRq, CcStmtEndRq)
        """
        msgs = {
            "bankmsgsrqv1": None,
            "creditcardmsgsrqv1": None,
            "invstmtmsgsrqv1": None,
        }

        sortkey = attrgetter("__class__.__name__")
        requests = sorted(requests, key=sortkey)
        for clsName, rqs in itertools.groupby(requests, key=sortkey):
            if clsName == "StmtEndRq":
                msgs["bankmsgsrqv1"] = BANKMSGSRQV1(*[
                    self.stmtendtrnrq(**dict(rq._asdict(), bankid=self.bankid))
                    for rq in rqs
                ])
            elif clsName == "CcStmtEndRq":
                msgs["creditcardmsgsrqv1"] = CREDITCARDMSGSRQV1(
                    *[self.ccstmtendtrnrq(**rq._asdict()) for rq in rqs])
            else:
                msg = "Not a *StmtEndRq: {}".format(clsName)
                raise ValueError(msg)

        signon = self.signon(user,
                             password,
                             language=language,
                             clientuid=clientuid,
                             appid=appid,
                             appver=appver)
        ofx = OFX(signonmsgsrqv1=signon, **msgs)
        return self.download(
            ofx,
            dryrun=dryrun,
            version=version,
            prettyprint=prettyprint,
            close_elements=close_elements,
            timeout=timeout,
        )
示例#8
0
 def aggregate(cls):
     return OFX(
         signonmsgsrqv1=msgsets.Signonmsgsrqv1TestCase.aggregate,
         signupmsgsrqv1=msgsets.Signupmsgsrqv1TestCase.aggregate,
         bankmsgsrqv1=msgsets.Bankmsgsrqv1TestCase.aggregate,
         creditcardmsgsrqv1=msgsets.Creditcardmsgsrqv1TestCase.aggregate,
         invstmtmsgsrqv1=msgsets.Invstmtmsgsrqv1TestCase.aggregate,
         interxfermsgsrqv1=msgsets.Interxfermsgsrqv1TestCase.aggregate,
         wirexfermsgsrqv1=msgsets.Wirexfermsgsrqv1TestCase.aggregate,
         emailmsgsrqv1=msgsets.Emailmsgsrqv1TestCase.aggregate,
         seclistmsgsrqv1=msgsets.Seclistmsgsrqv1TestCase.aggregate,
         profmsgsrqv1=msgsets.Profmsgsrqv1TestCase.aggregate)
示例#9
0
    def request_statements(self,
                           user,
                           password,
                           clientuid=None,
                           stmtrqs=None,
                           ccstmtrqs=None,
                           invstmtrqs=None,
                           dryrun=False,
                           prettyprint=None):
        """
        Package and send OFX statement requests (STMTRQ/CCSTMTRQ/INVSTMTRQ).

        Input *rqs are sequences of the corresponding namedtuples
        (StmtRq, CcStmtRq, InvStmtRq)
        """
        signonmsgs = self.signon(user, password, clientuid=clientuid)

        bankmsgs = None
        if stmtrqs:
            # bankid comes from OFXClient instance attribute,
            # not StmtRq namedtuple
            stmttrnrqs = [
                self.stmttrnrq(**dict(stmtrq._asdict(), bankid=self.bankid))
                for stmtrq in stmtrqs
            ]
            bankmsgs = BANKMSGSRQV1(*stmttrnrqs)

        creditcardmsgs = None
        if ccstmtrqs:
            ccstmttrnrqs = [
                self.ccstmttrnrq(**stmtrq._asdict()) for stmtrq in ccstmtrqs
            ]
            creditcardmsgs = CREDITCARDMSGSRQV1(*ccstmttrnrqs)

        invstmtmsgs = None
        if invstmtrqs:
            # brokerid comes from OFXClient instance attribute,
            # not StmtRq namedtuple
            invstmttrnrqs = [
                self.invstmttrnrq(
                    **dict(stmtrq._asdict(), brokerid=self.brokerid))
                for stmtrq in invstmtrqs
            ]
            invstmtmsgs = INVSTMTMSGSRQV1(*invstmttrnrqs)

        ofx = OFX(signonmsgsrqv1=signonmsgs,
                  bankmsgsrqv1=bankmsgs,
                  creditcardmsgsrqv1=creditcardmsgs,
                  invstmtmsgsrqv1=invstmtmsgs)
        return self.download(ofx, dryrun=dryrun, prettyprint=prettyprint)
示例#10
0
    def request_tax1099(
        self,
        password: str,
        *taxyears: str,
        acctnum: str = None,
        recid: str = None,
        gen_newfileuid: bool = True,
        dryrun: bool = False,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Request US federal income tax form 1099 (TAX1099RQ)
        """
        if dryrun:
            url = ""
        else:
            RqCls2url = self._get_service_urls(
                timeout=timeout,
                gen_newfileuid=gen_newfileuid,
            )

            # HACK FIXME
            # As a simplification, we assume that FIs handle all classes
            # of statement request from a single URL.
            urls = set(RqCls2url.values())
            assert len(urls) == 1
            url = urls.pop()

        logger.info("Creating tax 1099 request")
        signon = self.signon(password)

        rq = TAX1099RQ(*taxyears, recid=recid or None)
        msgs = TAX1099MSGSRQV1(TAX1099TRNRQ(trnuid=self.uuid, tax1099rq=rq))

        logger.debug(f"Wrapped tax 1099 request messages: {msgs}")

        ofx = OFX(signonmsgsrqv1=signon, tax1099msgsrqv1=msgs)

        if gen_newfileuid:
            newfileuid = self.uuid
        else:
            newfileuid = None

        return self.download(
            ofx,
            newfileuid=newfileuid,
            dryrun=dryrun,
            timeout=timeout,
            url=url,
        )
示例#11
0
    def request_accounts(
        self,
        password: str,
        dtacctup: datetime.datetime,
        dryrun: bool = False,
        version: Optional[int] = None,
        gen_newfileuid: bool = True,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Package and send OFX account info requests (ACCTINFORQ)
        """
        if dryrun:
            url = ""
        else:
            RqCls2url = self._get_service_urls(
                timeout=timeout,
                gen_newfileuid=gen_newfileuid,
            )

            # HACK FIXME
            # As a simplification, we assume that FIs handle all classes
            # of statement request from a single URL.
            urls = set(RqCls2url.values())
            assert len(urls) == 1
            url = urls.pop()

        logger.info("Creating account info request")
        signon = self.signon(password)

        acctinforq = ACCTINFORQ(dtacctup=dtacctup)
        acctinfotrnrq = ACCTINFOTRNRQ(trnuid=self.uuid, acctinforq=acctinforq)
        msgs = SIGNUPMSGSRQV1(acctinfotrnrq)

        logger.debug(f"Wrapped account info request messages: {msgs}")

        ofx = OFX(signonmsgsrqv1=signon, signupmsgsrqv1=msgs)

        if gen_newfileuid:
            newfileuid = self.uuid
        else:
            newfileuid = None

        return self.download(
            ofx,
            newfileuid=newfileuid,
            dryrun=dryrun,
            timeout=timeout,
            url=url,
        )
示例#12
0
    def request_accounts(self, user, password, dtacctup, clientuid=None,
                         dryrun=False, prettyprint=None, close_elements=True):
        """
        Package and send OFX account info requests (ACCTINFORQ)
        """
        signonmsgs = self.signon(user, password, clientuid=clientuid)
        acctinforq = ACCTINFORQ(dtacctup=dtacctup)
        acctinfotrnrq = ACCTINFOTRNRQ(trnuid=uuid.uuid4(),
                                      acctinforq=acctinforq)
        signupmsgs = SIGNUPMSGSRQV1(acctinfotrnrq)

        ofx = OFX(signonmsgsrqv1=signonmsgs,
                  signupmsgsrqv1=signupmsgs)
        return self.download(ofx, dryrun=dryrun, prettyprint=prettyprint,
                             close_elements=close_elements)
示例#13
0
    def request_profile(self, user=None, password=None, dryrun=False):
        """
        Package and send OFX profile requests (PROFRQ).
        """
        dtprofup = datetime.date(1990, 1, 1)
        profrq = PROFRQ(clientrouting='NONE', dtprofup=dtprofup)
        trnuid = uuid.uuid4()
        proftrnrq = PROFTRNRQ(trnuid=trnuid, profrq=profrq)
        msgs = PROFMSGSRQV1(proftrnrq)

        user = user or '{:0<32}'.format('anonymous')
        password = password or '{:0<32}'.format('anonymous')
        signonmsgs = self.signon(user, password)

        ofx = OFX(signonmsgsrqv1=signonmsgs, profmsgsrqv1=msgs)
        return self.download(ofx, dryrun=dryrun)
示例#14
0
    def _request_profile(
        self,
        dtprofup: Optional[datetime.datetime] = None,
        version: Optional[int] = None,
        gen_newfileuid: bool = True,
        prettyprint: Optional[bool] = None,
        close_elements: Optional[bool] = None,
        dryrun: bool = False,
        timeout: Optional[float] = None,
        url: Optional[str] = None,
    ) -> BytesIO:
        """Package and send OFX profile requests (PROFRQ)."""
        logger.info("Creating profile request")

        if dtprofup is None:
            dtprofup = datetime.datetime(1990, 1, 1, tzinfo=UTC)
        profrq = PROFRQ(clientrouting="NONE", dtprofup=dtprofup)
        proftrnrq = PROFTRNRQ(trnuid=self.uuid, profrq=profrq)

        logger.debug(f"Wrapped profile request: {proftrnrq}")

        user = password = AUTH_PLACEHOLDER
        signon = self.signon(password, userid=user)

        ofx = OFX(signonmsgsrqv1=signon, profmsgsrqv1=PROFMSGSRQV1(proftrnrq))

        if gen_newfileuid:
            newfileuid = self.uuid
        else:
            newfileuid = None

        return self.download(
            ofx,
            version=version,
            newfileuid=newfileuid,
            prettyprint=prettyprint,
            close_elements=close_elements,
            dryrun=dryrun,
            timeout=timeout,
            url=url,
        )
示例#15
0
    def request_accounts(self,
                         user,
                         password,
                         dtacctup,
                         language=None,
                         clientuid=None,
                         appid=None,
                         appver=None,
                         dryrun=False,
                         version=None,
                         prettyprint=False,
                         close_elements=True,
                         timeout=None):
        """
        Package and send OFX account info requests (ACCTINFORQ)
        """
        signon = self.signon(user,
                             password,
                             language=language,
                             clientuid=clientuid,
                             appid=appid,
                             appver=appver)

        acctinforq = ACCTINFORQ(dtacctup=dtacctup)
        acctinfotrnrq = ACCTINFOTRNRQ(trnuid=self.uuid, acctinforq=acctinforq)
        signupmsgs = SIGNUPMSGSRQV1(acctinfotrnrq)

        ofx = OFX(signonmsgsrqv1=signon, signupmsgsrqv1=signupmsgs)
        return self.download(
            ofx,
            dryrun=dryrun,
            version=version,
            prettyprint=prettyprint,
            close_elements=close_elements,
            timeout=timeout,
        )
示例#16
0
    def request_profile(
        self,
        version: Optional[int] = None,
        prettyprint: Optional[bool] = None,
        close_elements: Optional[bool] = None,
        dryrun: bool = False,
        verify_ssl: bool = True,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Package and send OFX profile requests (PROFRQ).

        ofxget.scan_profile() overrides version/prettyprint/close_elements.
        """
        logger.info("Creating profile request")

        dtprofup = datetime.datetime(1990, 1, 1, tzinfo=UTC)
        profrq = PROFRQ(clientrouting="NONE", dtprofup=dtprofup)
        proftrnrq = PROFTRNRQ(trnuid=self.uuid, profrq=profrq)

        logger.debug(f"Wrapped profile request: {proftrnrq}")

        user = password = AUTH_PLACEHOLDER
        signon = self.signon(password, userid=user)

        ofx = OFX(signonmsgsrqv1=signon, profmsgsrqv1=PROFMSGSRQV1(proftrnrq))

        return self.download(
            ofx,
            version=version,
            prettyprint=prettyprint,
            close_elements=close_elements,
            dryrun=dryrun,
            verify_ssl=verify_ssl,
            timeout=timeout,
        )
示例#17
0
    def request_statements(
        self,
        password: str,
        *requests: RequestParam,
        gen_newfileuid: bool = True,
        dryrun: bool = False,
        timeout: Optional[float] = None,
    ) -> BinaryIO:
        """
        Package and send OFX statement requests
        (STMTRQ/CCSTMTRQ/INVSTMTRQ/STMTENDRQ/CCSTMTENDRQ).
        """
        if dryrun:
            url = ""
        else:
            RqCls2url = self._get_service_urls(
                timeout=timeout,
                gen_newfileuid=gen_newfileuid,
            )

            # HACK FIXME
            # As a simplification, we assume that FIs handle all classes
            # of statement request from a single URL.
            urls = set(RqCls2url.values())
            assert len(urls) == 1
            url = urls.pop()

        logger.info(f"Creating statement requests for {requests}")
        # Group requests by type and pass to the appropriate *TRNRQ handler
        # function (see singledispatch setup below).
        #
        # Classes don't have rich comparison methods, so we can't sort by class.
        # As a proxy, we sort by class name, even though we actually group by class
        # so we can use it when iterating over groupby().
        sortKey = attrgetter("__class__.__name__")
        groupKey = attrgetter("__class__")
        trnrqs = [
            wrap_stmtrq(cls(), rqs, self)
            for cls, rqs in itertools.groupby(sorted(requests, key=sortKey),
                                              key=groupKey)
        ]

        # trnrqs is a pair of (models.*MSGSRQV1, [*TRNRQ])
        # Can't sort *MSGSRQV1 by class, either, so we use the same trick
        # of sorting by class name and grouping by class.
        def trnSortKey(pair):
            return pair[0].__name__

        trnGroupKey = itemgetter(0)
        trnrqs.sort(key=trnSortKey)

        # N.B. we need to annotate first arg as typing.Type here to indicate that
        # we're passing in a class not an instance.
        def msg_args(
            msgcls: Union[Type[BANKMSGSRQV1], Type[CREDITCARDMSGSRQV1],
                          Type[INVSTMTMSGSRQV1]],
            trnrqs: Iterator[Request],
        ) -> Tuple[str, Message]:
            trnrqs_ = list(itertools.chain.from_iterable(t[1] for t in trnrqs))
            attr_name = msgcls.__name__.lower()
            return (attr_name, msgcls(*trnrqs_))

        msgs = dict(
            msg_args(msgcls, _trnrqs)
            for msgcls, _trnrqs in itertools.groupby(trnrqs, key=trnGroupKey))
        logger.debug(f"Wrapped statement request messages: {msgs}")

        signon = self.signon(password)
        ofx = OFX(signonmsgsrqv1=signon, **msgs)

        if gen_newfileuid:
            newfileuid = self.uuid
        else:
            newfileuid = None

        return self.download(
            ofx,
            newfileuid=newfileuid,
            dryrun=dryrun,
            timeout=timeout,
            url=url,
        )