From 199a76d0b5d8fb8c61b49db73cd2b697841d84b6 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 16 Oct 2023 05:10:41 +0300
Subject: [PATCH 01/62] chore: add license
---
LICENSE.md | 674 +++++++++++++++++++++++++++++++++++++++++++++++++
pyproject.toml | 1 +
2 files changed, 675 insertions(+)
create mode 100644 LICENSE.md
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/pyproject.toml b/pyproject.toml
index 7efba17..99e0dd5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,7 @@
name = "ab-global-bot"
version = "0.1.0"
description = ""
+license = "GPL-3.0-or-later"
authors = ["Dan Sazonov "]
readme = "README.md"
From 121f449f7ad34bbf6d62a49a0ef4ffefdd34d175 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 16 Oct 2023 05:30:28 +0300
Subject: [PATCH 02/62] chore: create aiogram template
---
bot/main.py | 58 +++-
poetry.lock | 728 ++++++++++++++++++++++++++++++++++++++++++++++++-
pyproject.toml | 1 +
3 files changed, 781 insertions(+), 6 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index cab47fe..7cb1bab 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -1,6 +1,56 @@
-def print_hi(name):
- print(f'Hi, {name}')
+import asyncio
+import logging
+import sys
+from os import getenv
+from aiogram import Bot, Dispatcher, Router, types
+from aiogram.enums import ParseMode
+from aiogram.filters import CommandStart
+from aiogram.types import Message
+from aiogram.utils.markdown import hbold
-if __name__ == '__main__':
- print_hi('PyCharm')
+# Bot token can be obtained via https://t.me/BotFather
+TOKEN = getenv("BOT_TOKEN")
+
+# All handlers should be attached to the Router (or Dispatcher)
+dp = Dispatcher()
+
+
+@dp.message(CommandStart())
+async def command_start_handler(message: Message) -> None:
+ """
+ This handler receives messages with `/start` command
+ """
+ # Most event objects have aliases for API methods that can be called in events' context
+ # For example if you want to answer to incoming message you can use `message.answer(...)` alias
+ # and the target chat will be passed to :ref:`aiogram.methods.send_message.SendMessage`
+ # method automatically or call API method directly via
+ # Bot instance: `bot.send_message(chat_id=message.chat.id, ...)`
+ await message.answer(f"Hello, {hbold(message.from_user.full_name)}!")
+
+
+@dp.message()
+async def echo_handler(message: types.Message) -> None:
+ """
+ Handler will forward receive a message back to the sender
+
+ By default, message handler will handle all message types (like a text, photo, sticker etc.)
+ """
+ try:
+ # Send a copy of the received message
+ await message.send_copy(chat_id=message.chat.id)
+ except TypeError:
+ # But not all the types is supported to be copied so need to handle it
+ await message.answer("Nice try!")
+
+
+async def main() -> None:
+ # Initialize Bot instance with a default parse mode which will be passed to all API calls
+ bot = Bot(TOKEN, parse_mode=ParseMode.HTML)
+ # And the run events dispatching
+ await dp.start_polling(bot)
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.INFO, stream=sys.stdout)
+ asyncio.run(main())
diff --git a/poetry.lock b/poetry.lock
index 24cf95d..b917cd6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,7 +1,731 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
-package = []
+
+[[package]]
+name = "aiofiles"
+version = "23.1.0"
+description = "File support for asyncio."
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"},
+ {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"},
+]
+
+[[package]]
+name = "aiogram"
+version = "3.1.1"
+description = "Modern and fully asynchronous framework for Telegram Bot API"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "aiogram-3.1.1-py3-none-any.whl", hash = "sha256:c381cbf252bca4c8e9095f02283e44d11286c982b923407473fb2457c0dab109"},
+ {file = "aiogram-3.1.1.tar.gz", hash = "sha256:4e183d34adf28445d6c6501eb803c7b0b52fd4a09c89bd715b2df9dbd9fa8126"},
+]
+
+[package.dependencies]
+aiofiles = ">=23.1.0,<23.2.0"
+aiohttp = ">=3.8.5,<3.9.0"
+certifi = ">=2023.7.22"
+magic-filter = ">=1.0.11,<1.1.0"
+pydantic = ">=2.1.1,<2.4"
+typing-extensions = ">=4.7.1,<4.8.0"
+
+[package.extras]
+cli = ["aiogram-cli (>=1.0,<2.0)"]
+dev = ["black (>=23.7.0,<23.8.0)", "isort (>=5.11,<6.0)", "mypy (>=1.4.1,<1.5.0)", "packaging (>=23.0,<24.0)", "pre-commit (>=3.3.3,<3.4.0)", "ruff (>=0.0.280,<0.1.0)", "toml (>=0.10.2,<0.11.0)", "towncrier (>=23.6.0,<23.7.0)"]
+docs = ["furo (>=2023.7.26,<2023.8.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.15.1,<2.16.0)", "pymdown-extensions (>=10.1,<11.0)", "sphinx (>=7.1.1,<7.2.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.0.1,<2.1.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.3.2a0,<0.4.0)", "towncrier (>=23.6.0,<23.7.0)"]
+fast = ["uvloop (>=0.17.0)"]
+i18n = ["babel (>=2.12.1,<2.13.0)"]
+proxy = ["aiohttp-socks (>=0.8.0,<0.9.0)"]
+redis = ["redis (>=4.6.0,<4.7.0)"]
+test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.18,<4.0)", "pytest (>=7.4.0,<7.5.0)", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-asyncio (>=0.21.0,<0.22.0)", "pytest-cov (>=4.0.0,<4.1.0)", "pytest-html (>=3.2.0,<3.3.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.10.0,<3.11.0)", "pytest-mypy (>=0.10.0,<0.11.0)", "pytz (>=2022.7.1,<2022.8.0)"]
+
+[[package]]
+name = "aiohttp"
+version = "3.8.6"
+description = "Async http client/server framework (asyncio)"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"},
+ {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"},
+ {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"},
+ {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"},
+ {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"},
+ {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"},
+ {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"},
+ {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"},
+ {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"},
+ {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"},
+ {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"},
+ {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"},
+ {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"},
+ {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"},
+ {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"},
+ {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"},
+ {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"},
+ {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"},
+ {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"},
+ {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"},
+ {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"},
+ {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"},
+ {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"},
+ {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"},
+ {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"},
+ {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"},
+ {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"},
+ {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"},
+ {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"},
+ {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"},
+ {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"},
+ {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"},
+ {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"},
+ {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"},
+ {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"},
+ {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"},
+ {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"},
+ {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"},
+ {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"},
+ {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"},
+ {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"},
+ {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"},
+ {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"},
+ {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"},
+ {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"},
+ {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"},
+ {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"},
+ {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"},
+ {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"},
+ {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"},
+ {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"},
+ {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"},
+ {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"},
+ {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"},
+ {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"},
+ {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"},
+ {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"},
+ {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"},
+ {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"},
+ {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"},
+ {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"},
+ {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"},
+ {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"},
+]
+
+[package.dependencies]
+aiosignal = ">=1.1.2"
+async-timeout = ">=4.0.0a3,<5.0"
+attrs = ">=17.3.0"
+charset-normalizer = ">=2.0,<4.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+yarl = ">=1.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli", "aiodns", "cchardet"]
+
+[[package]]
+name = "aiosignal"
+version = "1.3.1"
+description = "aiosignal: a list of registered asynchronous callbacks"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
+ {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
+]
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "annotated-types"
+version = "0.6.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
+ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
+]
+
+[[package]]
+name = "async-timeout"
+version = "4.0.3"
+description = "Timeout context manager for asyncio programs"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
+ {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
+]
+
+[[package]]
+name = "attrs"
+version = "23.1.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
+ {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
+]
+
+[package.extras]
+cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
+dev = ["attrs[docs,tests]", "pre-commit"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
+tests = ["attrs[tests-no-zope]", "zope-interface"]
+tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+
+[[package]]
+name = "certifi"
+version = "2023.7.22"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
+ {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.3.0"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"},
+ {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"},
+ {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"},
+ {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"},
+ {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"},
+ {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"},
+ {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"},
+ {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"},
+]
+
+[[package]]
+name = "frozenlist"
+version = "1.4.0"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"},
+ {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"},
+ {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"},
+ {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"},
+ {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"},
+ {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"},
+ {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"},
+ {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"},
+ {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"},
+ {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"},
+ {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"},
+ {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"},
+ {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"},
+ {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"},
+ {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"},
+ {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"},
+ {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"},
+ {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"},
+ {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"},
+ {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"},
+ {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"},
+ {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"},
+ {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"},
+ {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"},
+ {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"},
+ {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"},
+ {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"},
+ {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"},
+ {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
+]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+
+[[package]]
+name = "magic-filter"
+version = "1.0.12"
+description = ""
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6"},
+ {file = "magic_filter-1.0.12.tar.gz", hash = "sha256:4751d0b579a5045d1dc250625c4c508c18c3def5ea6afaf3957cb4530d03f7f9"},
+]
+
+[package.extras]
+dev = ["black (>=22.8.0,<22.9.0)", "flake8 (>=5.0.4,<5.1.0)", "isort (>=5.11.5,<5.12.0)", "mypy (>=1.4.1,<1.5.0)", "pre-commit (>=2.20.0,<2.21.0)", "pytest (>=7.1.3,<7.2.0)", "pytest-cov (>=3.0.0,<3.1.0)", "pytest-html (>=3.1.1,<3.2.0)", "types-setuptools (>=65.3.0,<65.4.0)"]
+
+[[package]]
+name = "multidict"
+version = "6.0.4"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
+ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
+ {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
+ {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
+ {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
+ {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
+ {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
+ {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
+ {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
+ {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
+ {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
+ {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
+ {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
+ {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
+ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.3.0"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"},
+ {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.4.0"
+pydantic-core = "2.6.3"
+typing-extensions = ">=4.6.1"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.6.3"
+description = ""
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"},
+ {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"},
+ {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"},
+ {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"},
+ {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"},
+ {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"},
+ {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"},
+ {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"},
+ {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"},
+ {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"},
+ {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"},
+ {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"},
+ {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"},
+ {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"},
+ {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"},
+ {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+
+[[package]]
+name = "typing-extensions"
+version = "4.7.1"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
+ {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
+]
+
+[[package]]
+name = "yarl"
+version = "1.9.2"
+description = "Yet another URL library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"},
+ {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"},
+ {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"},
+ {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"},
+ {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"},
+ {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"},
+ {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"},
+ {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"},
+ {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"},
+ {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"},
+ {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"},
+ {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"},
+ {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"},
+ {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"},
+ {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"},
+ {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"},
+ {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"},
+ {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"},
+ {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"},
+ {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"},
+ {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"},
+ {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"},
+ {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"},
+ {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"},
+ {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"},
+ {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"},
+ {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"},
+ {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"},
+ {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"},
+ {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"},
+ {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"},
+ {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"},
+ {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"},
+ {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"},
+ {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"},
+ {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"},
+ {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"},
+ {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"},
+ {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"},
+ {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"},
+ {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"},
+ {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"},
+ {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"},
+ {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"},
+ {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"},
+ {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"},
+ {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"},
+ {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"},
+ {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"},
+ {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"},
+ {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"},
+ {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"},
+ {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"},
+ {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"},
+ {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"},
+ {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"},
+ {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"},
+ {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"},
+ {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"},
+ {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"},
+ {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"},
+ {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"},
+ {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"},
+ {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"},
+ {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"},
+ {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"},
+ {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"},
+ {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"},
+ {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"},
+ {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"},
+ {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"},
+ {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"},
+ {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"},
+ {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"},
+]
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "81b2fa642d7f2d1219cf80112ace12d689d053d81be7f7addb98144d56fc0fb2"
+content-hash = "3fa7f792e592b90c822b90b41e4438e3d77a1b27d19681df8f8f1ecf5fcc8233"
diff --git a/pyproject.toml b/pyproject.toml
index 99e0dd5..10702cd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,6 +8,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
+aiogram = "^3.1.1"
[build-system]
From 60d8c0dac90e2164d50ed7dec0676cd97c668f41 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 06:35:00 +0300
Subject: [PATCH 03/62] chore: remove comments
---
bot/main.py | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 7cb1bab..c63c3de 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -9,10 +9,8 @@
from aiogram.types import Message
from aiogram.utils.markdown import hbold
-# Bot token can be obtained via https://t.me/BotFather
-TOKEN = getenv("BOT_TOKEN")
-# All handlers should be attached to the Router (or Dispatcher)
+TOKEN = getenv("BOT_TOKEN")
dp = Dispatcher()
@@ -21,11 +19,6 @@ async def command_start_handler(message: Message) -> None:
"""
This handler receives messages with `/start` command
"""
- # Most event objects have aliases for API methods that can be called in events' context
- # For example if you want to answer to incoming message you can use `message.answer(...)` alias
- # and the target chat will be passed to :ref:`aiogram.methods.send_message.SendMessage`
- # method automatically or call API method directly via
- # Bot instance: `bot.send_message(chat_id=message.chat.id, ...)`
await message.answer(f"Hello, {hbold(message.from_user.full_name)}!")
@@ -37,17 +30,13 @@ async def echo_handler(message: types.Message) -> None:
By default, message handler will handle all message types (like a text, photo, sticker etc.)
"""
try:
- # Send a copy of the received message
await message.send_copy(chat_id=message.chat.id)
except TypeError:
- # But not all the types is supported to be copied so need to handle it
await message.answer("Nice try!")
async def main() -> None:
- # Initialize Bot instance with a default parse mode which will be passed to all API calls
bot = Bot(TOKEN, parse_mode=ParseMode.HTML)
- # And the run events dispatching
await dp.start_polling(bot)
From 5ea5da7eeb1faf8a84c10bf178ceaf8c698667ca Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 06:44:01 +0300
Subject: [PATCH 04/62] feat: crate config file and add env file
---
.env.example | 2 ++
bot/config.py | 7 +++++++
bot/main.py | 5 ++---
poetry.lock | 16 +++++++++++++++-
pyproject.toml | 1 +
5 files changed, 27 insertions(+), 4 deletions(-)
create mode 100644 .env.example
create mode 100644 bot/config.py
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..69858fe
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,2 @@
+BOT_TOKEN = ...
+ADMIN_ID = ...
diff --git a/bot/config.py b/bot/config.py
new file mode 100644
index 0000000..a502692
--- /dev/null
+++ b/bot/config.py
@@ -0,0 +1,7 @@
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+
+BOT_TOKEN = os.getenv("BOT_TOKEN")
+ADMIN_ID = os.getenv("ADMIN_ID")
diff --git a/bot/main.py b/bot/main.py
index c63c3de..010f308 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -1,7 +1,6 @@
import asyncio
import logging
import sys
-from os import getenv
from aiogram import Bot, Dispatcher, Router, types
from aiogram.enums import ParseMode
@@ -9,8 +8,8 @@
from aiogram.types import Message
from aiogram.utils.markdown import hbold
+import config
-TOKEN = getenv("BOT_TOKEN")
dp = Dispatcher()
@@ -36,7 +35,7 @@ async def echo_handler(message: types.Message) -> None:
async def main() -> None:
- bot = Bot(TOKEN, parse_mode=ParseMode.HTML)
+ bot = Bot(config.BOT_TOKEN, parse_mode=ParseMode.HTML)
await dp.start_polling(bot)
diff --git a/poetry.lock b/poetry.lock
index b917cd6..b127b18 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -627,6 +627,20 @@ files = [
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+[[package]]
+name = "python-dotenv"
+version = "1.0.0"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+ {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
[[package]]
name = "typing-extensions"
version = "4.7.1"
@@ -728,4 +742,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "3fa7f792e592b90c822b90b41e4438e3d77a1b27d19681df8f8f1ecf5fcc8233"
+content-hash = "28030d85726dcb2844a70e043a4f80ca8278f2fdb87cb5295154b85318868508"
diff --git a/pyproject.toml b/pyproject.toml
index 10702cd..975a3a8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,6 +9,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
aiogram = "^3.1.1"
+python-dotenv = "^1.0.0"
[build-system]
From 4a57bc08e4f84f7a91614c4323d5f7c346400e14 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 06:49:56 +0300
Subject: [PATCH 05/62] refactor: add dataclass for env vars
---
bot/config.py | 18 +++++++++++++++---
bot/main.py | 2 +-
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/bot/config.py b/bot/config.py
index a502692..5e445cd 100644
--- a/bot/config.py
+++ b/bot/config.py
@@ -1,7 +1,19 @@
import os
+from dataclasses import dataclass
+
from dotenv import load_dotenv
-load_dotenv()
-BOT_TOKEN = os.getenv("BOT_TOKEN")
-ADMIN_ID = os.getenv("ADMIN_ID")
+@dataclass
+class Bot:
+ bot_token: str
+ admin_id: int
+
+
+def _get_settings():
+ load_dotenv()
+ return Bot(bot_token=os.getenv("BOT_TOKEN"),
+ admin_id=int(os.getenv("ADMIN_ID")))
+
+
+settings = _get_settings()
diff --git a/bot/main.py b/bot/main.py
index 010f308..6285438 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -35,7 +35,7 @@ async def echo_handler(message: types.Message) -> None:
async def main() -> None:
- bot = Bot(config.BOT_TOKEN, parse_mode=ParseMode.HTML)
+ bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
await dp.start_polling(bot)
From 8a0acfc2aa53b726e3bc515af00811df0ccaef7b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 07:01:25 +0300
Subject: [PATCH 06/62] refactor: move the message text to a separate file
---
bot/main.py | 17 ++---------------
bot/messages.py | 4 ++++
2 files changed, 6 insertions(+), 15 deletions(-)
create mode 100644 bot/messages.py
diff --git a/bot/main.py b/bot/main.py
index 6285438..459cad1 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -6,9 +6,9 @@
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart
from aiogram.types import Message
-from aiogram.utils.markdown import hbold
import config
+import messages
dp = Dispatcher()
@@ -18,20 +18,7 @@ async def command_start_handler(message: Message) -> None:
"""
This handler receives messages with `/start` command
"""
- await message.answer(f"Hello, {hbold(message.from_user.full_name)}!")
-
-
-@dp.message()
-async def echo_handler(message: types.Message) -> None:
- """
- Handler will forward receive a message back to the sender
-
- By default, message handler will handle all message types (like a text, photo, sticker etc.)
- """
- try:
- await message.send_copy(chat_id=message.chat.id)
- except TypeError:
- await message.answer("Nice try!")
+ await message.answer(messages.START)
async def main() -> None:
diff --git a/bot/messages.py b/bot/messages.py
new file mode 100644
index 0000000..78ecf70
--- /dev/null
+++ b/bot/messages.py
@@ -0,0 +1,4 @@
+from aiogram.utils.markdown import hbold
+
+START = f"{hbold('Hello!')}"
+
From 6053e6dd9211a8705d402acbccb1f0057d82105e Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 07:12:24 +0300
Subject: [PATCH 07/62] feat: help command template
---
bot/main.py | 9 +++++++--
bot/messages.py | 2 +-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 459cad1..b45b12d 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -4,7 +4,7 @@
from aiogram import Bot, Dispatcher, Router, types
from aiogram.enums import ParseMode
-from aiogram.filters import CommandStart
+from aiogram.filters import Command
from aiogram.types import Message
import config
@@ -13,7 +13,7 @@
dp = Dispatcher()
-@dp.message(CommandStart())
+@dp.message(Command("start"))
async def command_start_handler(message: Message) -> None:
"""
This handler receives messages with `/start` command
@@ -21,6 +21,11 @@ async def command_start_handler(message: Message) -> None:
await message.answer(messages.START)
+@dp.message(Command("help"))
+async def command_help_handler(message: Message) -> None:
+ await message.answer(messages.HELP)
+
+
async def main() -> None:
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
await dp.start_polling(bot)
diff --git a/bot/messages.py b/bot/messages.py
index 78ecf70..4dc79f8 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -1,4 +1,4 @@
from aiogram.utils.markdown import hbold
START = f"{hbold('Hello!')}"
-
+HELP = "Sample of /help message"
From 7fd13acb9bd9edc44a1114524999201a59ecf0c1 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 07:13:19 +0300
Subject: [PATCH 08/62] feat: stop command template
---
bot/main.py | 5 +++++
bot/messages.py | 3 +++
2 files changed, 8 insertions(+)
diff --git a/bot/main.py b/bot/main.py
index b45b12d..f8bd115 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -26,6 +26,11 @@ async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
+@dp.message(Command("stop"))
+async def command_stop_handler(message: Message) -> None:
+ await message.answer(messages.STOP)
+
+
async def main() -> None:
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
await dp.start_polling(bot)
diff --git a/bot/messages.py b/bot/messages.py
index 4dc79f8..69e841b 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -1,4 +1,7 @@
from aiogram.utils.markdown import hbold
START = f"{hbold('Hello!')}"
+
HELP = "Sample of /help message"
+
+STOP = "Sample of /stop message"
From ea7b4298d8e2645bb3a7cb24c9e5c8ff29d4a78e Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 07:17:18 +0300
Subject: [PATCH 09/62] feat: unexpected message answer template
---
bot/main.py | 5 +++++
bot/messages.py | 2 ++
2 files changed, 7 insertions(+)
diff --git a/bot/main.py b/bot/main.py
index f8bd115..9dc23ff 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -31,6 +31,11 @@ async def command_stop_handler(message: Message) -> None:
await message.answer(messages.STOP)
+@dp.message()
+async def unknown_command_handler(message: Message) -> None:
+ await message.answer(messages.UNKNOWN)
+
+
async def main() -> None:
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
await dp.start_polling(bot)
diff --git a/bot/messages.py b/bot/messages.py
index 69e841b..4786d36 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -5,3 +5,5 @@
HELP = "Sample of /help message"
STOP = "Sample of /stop message"
+
+UNKNOWN = "Unknown command, type /help"
From 65f614ba91766273be0fb9f88906d9b35c60e324 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Wed, 18 Oct 2023 07:42:08 +0300
Subject: [PATCH 10/62] feat: start/stop functions
---
bot/main.py | 14 +++++++++++++-
bot/messages.py | 4 ++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/bot/main.py b/bot/main.py
index 9dc23ff..023235e 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -11,6 +11,19 @@
import messages
dp = Dispatcher()
+bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
+
+
+@dp.startup()
+async def on_startup():
+ # await config.set_commands(dp)
+ await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_START)
+
+
+@dp.shutdown()
+async def on_shutdown():
+ await bot.close()
+ await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_STOP)
@dp.message(Command("start"))
@@ -37,7 +50,6 @@ async def unknown_command_handler(message: Message) -> None:
async def main() -> None:
- bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
await dp.start_polling(bot)
diff --git a/bot/messages.py b/bot/messages.py
index 4786d36..ea8d5e2 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -7,3 +7,7 @@
STOP = "Sample of /stop message"
UNKNOWN = "Unknown command, type /help"
+
+ON_START = "Бот запущен"
+
+ON_STOP = "Бот остановлен"
From 4d86d5ae8a9762b3df51e9c046990eb73583c9d7 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 12:58:23 +0300
Subject: [PATCH 11/62] chore: install peewee
---
poetry.lock | 12 +++++++++++-
pyproject.toml | 1 +
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/poetry.lock b/poetry.lock
index b127b18..3b08a4c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -490,6 +490,16 @@ files = [
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
]
+[[package]]
+name = "peewee"
+version = "3.17.0"
+description = "a little orm"
+optional = false
+python-versions = "*"
+files = [
+ {file = "peewee-3.17.0.tar.gz", hash = "sha256:3a56967f28a43ca7a4287f4803752aeeb1a57a08dee2e839b99868181dfb5df8"},
+]
+
[[package]]
name = "pydantic"
version = "2.3.0"
@@ -742,4 +752,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "28030d85726dcb2844a70e043a4f80ca8278f2fdb87cb5295154b85318868508"
+content-hash = "66f7659db9c20f24230b90bf5c9d1ab3f236ca48f9f67c40e109785db2a449fc"
diff --git a/pyproject.toml b/pyproject.toml
index 975a3a8..afa363c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,6 +10,7 @@ readme = "README.md"
python = "^3.11"
aiogram = "^3.1.1"
python-dotenv = "^1.0.0"
+peewee = "^3.17.0"
[build-system]
From 05620e43585e6c3e3b0c48d8a7f2ef765342909b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 13:47:27 +0300
Subject: [PATCH 12/62] chore: add db files to gitignore
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index 2d0653a..7b574d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,3 +160,5 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
+# database source files
+bot/*.db
\ No newline at end of file
From 3703f2e1698f2d643c9a3339bab2a5e05c3d30c1 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 13:47:48 +0300
Subject: [PATCH 13/62] feat: create data models
---
bot/db.py | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 bot/db.py
diff --git a/bot/db.py b/bot/db.py
new file mode 100644
index 0000000..2c8bb0d
--- /dev/null
+++ b/bot/db.py
@@ -0,0 +1,39 @@
+import peewee as pw
+
+db = pw.SqliteDatabase('data.db')
+
+
+class BaseModel(pw.Model):
+ class Meta:
+ database = db
+
+
+class User(BaseModel):
+ id = pw.PrimaryKeyField(unique=True)
+ tg_id = pw.BigIntegerField(unique=True)
+ date_reg = pw.DateTimeField()
+ date_act = pw.DateTimeField()
+ resp_num = pw.IntegerField()
+
+ class Meta:
+ db_table = 'users'
+ order_by = 'resp_num'
+
+
+class Word(BaseModel):
+ id = pw.PrimaryKeyField(unique=True)
+ word = pw.CharField()
+ show_num = pw.IntegerField()
+ vote_num = pw.IntegerField()
+
+ class Meta:
+ db_table = 'words'
+
+
+def _create_db():
+ # only for tests
+ with db:
+ db.create_tables([User, Word])
+
+
+_create_db()
From 74483b868b61adde6218eb52ca4b21f81b9f323f Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 13:53:59 +0300
Subject: [PATCH 14/62] refactor: models in another module
---
bot/db.py | 33 ++-------------------------------
bot/models.py | 33 +++++++++++++++++++++++++++++++++
2 files changed, 35 insertions(+), 31 deletions(-)
create mode 100644 bot/models.py
diff --git a/bot/db.py b/bot/db.py
index 2c8bb0d..65bfb65 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,39 +1,10 @@
-import peewee as pw
-
-db = pw.SqliteDatabase('data.db')
-
-
-class BaseModel(pw.Model):
- class Meta:
- database = db
-
-
-class User(BaseModel):
- id = pw.PrimaryKeyField(unique=True)
- tg_id = pw.BigIntegerField(unique=True)
- date_reg = pw.DateTimeField()
- date_act = pw.DateTimeField()
- resp_num = pw.IntegerField()
-
- class Meta:
- db_table = 'users'
- order_by = 'resp_num'
-
-
-class Word(BaseModel):
- id = pw.PrimaryKeyField(unique=True)
- word = pw.CharField()
- show_num = pw.IntegerField()
- vote_num = pw.IntegerField()
-
- class Meta:
- db_table = 'words'
+from models import models_list, db
def _create_db():
# only for tests
with db:
- db.create_tables([User, Word])
+ db.create_tables(models_list)
_create_db()
diff --git a/bot/models.py b/bot/models.py
new file mode 100644
index 0000000..10c17e3
--- /dev/null
+++ b/bot/models.py
@@ -0,0 +1,33 @@
+from peewee import Model, PrimaryKeyField, BigIntegerField, DateTimeField, IntegerField, CharField, SqliteDatabase
+
+db = SqliteDatabase('data.db')
+
+
+class BaseModel(Model):
+ class Meta:
+ database = db
+
+
+class User(BaseModel):
+ id = PrimaryKeyField(unique=True)
+ tg_id = BigIntegerField(unique=True)
+ date_reg = DateTimeField()
+ date_act = DateTimeField()
+ resp_num = IntegerField()
+
+ class Meta:
+ db_table = 'users'
+ order_by = 'resp_num'
+
+
+class Word(BaseModel):
+ id = PrimaryKeyField(unique=True)
+ word = CharField()
+ show_num = IntegerField()
+ vote_num = IntegerField()
+
+ class Meta:
+ db_table = 'words'
+
+
+models_list = [User, Word]
From 1bd0e9536be5e18674b0d02e9d5fb85a2d1671bf Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 14:12:39 +0300
Subject: [PATCH 15/62] refactor: db file replace to the root
---
.gitignore | 2 +-
bot/models.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index 7b574d9..2676c28 100644
--- a/.gitignore
+++ b/.gitignore
@@ -161,4 +161,4 @@ cython_debug/
.idea/
# database source files
-bot/*.db
\ No newline at end of file
+*.db
\ No newline at end of file
diff --git a/bot/models.py b/bot/models.py
index 10c17e3..b124496 100644
--- a/bot/models.py
+++ b/bot/models.py
@@ -1,6 +1,6 @@
from peewee import Model, PrimaryKeyField, BigIntegerField, DateTimeField, IntegerField, CharField, SqliteDatabase
-db = SqliteDatabase('data.db')
+db = SqliteDatabase('../data.db')
class BaseModel(Model):
From 56fc46bcc9de5f20ec2a913a7e6e602f309ab36b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 14:15:29 +0300
Subject: [PATCH 16/62] feat: create file_to_list func
---
bot/services.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100644 bot/services.py
diff --git a/bot/services.py b/bot/services.py
new file mode 100644
index 0000000..7827978
--- /dev/null
+++ b/bot/services.py
@@ -0,0 +1,13 @@
+def _file_to_list(path: str) -> list[str]:
+ """
+ Converts a txt file with words on separate lines to a list of strings.
+ Empty strings would be deleted
+
+ :param path: absolute or relative path to the txt file
+ :return: list of str
+ """
+ with open(path, 'r', encoding='utf-8') as f:
+ file_content = f.read().split('\n')
+ words = list(filter(None, file_content)) # remove empty strings
+
+ return words
\ No newline at end of file
From a61d577e8b533218c976a65b2ba9fd242bb25734 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 14:31:23 +0300
Subject: [PATCH 17/62] feat: check the existence of data file
---
.gitignore | 5 +++--
bot/services.py | 17 ++++++++++++++---
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore
index 2676c28..810bd5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,5 +160,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
-# database source files
-*.db
\ No newline at end of file
+# database and data source files
+*.db
+words.txt
diff --git a/bot/services.py b/bot/services.py
index 7827978..09367d8 100644
--- a/bot/services.py
+++ b/bot/services.py
@@ -1,13 +1,24 @@
-def _file_to_list(path: str) -> list[str]:
+import os.path
+
+
+def _check_data_file(path: str):
+ if not os.path.isfile(path):
+ print("Can't read data file with words")
+ exit(1)
+
+
+def get_words() -> list[str]:
"""
Converts a txt file with words on separate lines to a list of strings.
Empty strings would be deleted
- :param path: absolute or relative path to the txt file
:return: list of str
"""
+ path = '../words.txt'
+ _check_data_file(path)
+
with open(path, 'r', encoding='utf-8') as f:
file_content = f.read().split('\n')
words = list(filter(None, file_content)) # remove empty strings
- return words
\ No newline at end of file
+ return words
From 246763ebb756c2056745fef71912f82a2927bc07 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 18:15:00 +0300
Subject: [PATCH 18/62] feat: create tables func sample
---
bot/db.py | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/bot/db.py b/bot/db.py
index 65bfb65..b4d8a18 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,10 +1,6 @@
from models import models_list, db
-def _create_db():
- # only for tests
+def create_tables():
with db:
db.create_tables(models_list)
-
-
-_create_db()
From 321223f2ce74189a1d83e279420b668e118b0019 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 18:18:13 +0300
Subject: [PATCH 19/62] refactor: db path replace to the config
---
bot/config.py | 2 ++
bot/models.py | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/bot/config.py b/bot/config.py
index 5e445cd..c8717aa 100644
--- a/bot/config.py
+++ b/bot/config.py
@@ -17,3 +17,5 @@ def _get_settings():
settings = _get_settings()
+
+DB_FILE = '../data.db'
diff --git a/bot/models.py b/bot/models.py
index b124496..8953afc 100644
--- a/bot/models.py
+++ b/bot/models.py
@@ -1,6 +1,7 @@
from peewee import Model, PrimaryKeyField, BigIntegerField, DateTimeField, IntegerField, CharField, SqliteDatabase
+import config
-db = SqliteDatabase('../data.db')
+db = SqliteDatabase(config.DB_FILE)
class BaseModel(Model):
From 6ca318efe79ef8c10ed2f3ab889d16796d589bca Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 18:21:28 +0300
Subject: [PATCH 20/62] feat: sample of functions
---
bot/db.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/bot/db.py b/bot/db.py
index b4d8a18..b547897 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,6 +1,19 @@
from models import models_list, db
+def _has_db() -> bool:
+ pass
+
+
+def _add_words() -> None:
+ pass
+
+
def create_tables():
+ db_prev_state = _has_db()
+
with db:
db.create_tables(models_list)
+
+ if not db_prev_state:
+ _add_words()
From 30310dee15216f95ca56d6c691c1a54666044f33 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 18:24:20 +0300
Subject: [PATCH 21/62] feat: check the data file func
---
bot/db.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index b547897..ffa8f60 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,8 +1,11 @@
+import os
+
from models import models_list, db
+import config
def _has_db() -> bool:
- pass
+ return os.path.isfile(config.DB_FILE)
def _add_words() -> None:
From 1cd1ea728f6867e11fcd8ffdb61371d4cc71dd3d Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 18:40:53 +0300
Subject: [PATCH 22/62] chore: add icecream to dev dep
---
poetry.lock | 86 +++++++++++++++++++++++++++++++++++++++++++++++++-
pyproject.toml | 3 ++
2 files changed, 88 insertions(+), 1 deletion(-)
diff --git a/poetry.lock b/poetry.lock
index 3b08a4c..88a7430 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -173,6 +173,23 @@ files = [
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
]
+[[package]]
+name = "asttokens"
+version = "2.4.0"
+description = "Annotate AST trees with source code positions"
+optional = false
+python-versions = "*"
+files = [
+ {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"},
+ {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"},
+]
+
+[package.dependencies]
+six = ">=1.12.0"
+
+[package.extras]
+test = ["astroid", "pytest"]
+
[[package]]
name = "async-timeout"
version = "4.0.3"
@@ -312,6 +329,31 @@ files = [
{file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"},
]
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "executing"
+version = "2.0.0"
+description = "Get the currently executing AST node of a frame, and other information"
+optional = false
+python-versions = "*"
+files = [
+ {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"},
+ {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"},
+]
+
+[package.extras]
+tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
+
[[package]]
name = "frozenlist"
version = "1.4.0"
@@ -382,6 +424,23 @@ files = [
{file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
]
+[[package]]
+name = "icecream"
+version = "2.1.3"
+description = "Never use print() to debug again; inspect variables, expressions, and program execution with a single, simple function call."
+optional = false
+python-versions = "*"
+files = [
+ {file = "icecream-2.1.3-py2.py3-none-any.whl", hash = "sha256:757aec31ad4488b949bc4f499d18e6e5973c40cc4d4fc607229e78cfaec94c34"},
+ {file = "icecream-2.1.3.tar.gz", hash = "sha256:0aa4a7c3374ec36153a1d08f81e3080e83d8ac1eefd97d2f4fe9544e8f9b49de"},
+]
+
+[package.dependencies]
+asttokens = ">=2.0.1"
+colorama = ">=0.3.9"
+executing = ">=0.3.1"
+pygments = ">=2.2.0"
+
[[package]]
name = "idna"
version = "3.4"
@@ -637,6 +696,20 @@ files = [
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+[[package]]
+name = "pygments"
+version = "2.16.1"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"},
+ {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"},
+]
+
+[package.extras]
+plugins = ["importlib-metadata"]
+
[[package]]
name = "python-dotenv"
version = "1.0.0"
@@ -651,6 +724,17 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
[[package]]
name = "typing-extensions"
version = "4.7.1"
@@ -752,4 +836,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "66f7659db9c20f24230b90bf5c9d1ab3f236ca48f9f67c40e109785db2a449fc"
+content-hash = "e2d5dc24246b9340bc3def77d9f145dbeb9eeaf45d2343854f92dd8202a72b62"
diff --git a/pyproject.toml b/pyproject.toml
index afa363c..e0e549e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,9 @@ python-dotenv = "^1.0.0"
peewee = "^3.17.0"
+[tool.poetry.group.dev.dependencies]
+icecream = "^2.1.3"
+
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
From 07e9e62b8a06db61a036df282e5e435049da2f20 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 19:10:26 +0300
Subject: [PATCH 23/62] refactor: replace db state flag
---
bot/db.py | 13 +++----------
bot/models.py | 5 ++++-
2 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/bot/db.py b/bot/db.py
index ffa8f60..84391e2 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,11 +1,4 @@
-import os
-
-from models import models_list, db
-import config
-
-
-def _has_db() -> bool:
- return os.path.isfile(config.DB_FILE)
+from models import models_list, db, Word, prev_state
def _add_words() -> None:
@@ -13,10 +6,10 @@ def _add_words() -> None:
def create_tables():
- db_prev_state = _has_db()
+ has_db = prev_state
with db:
db.create_tables(models_list)
- if not db_prev_state:
+ if not has_db:
_add_words()
diff --git a/bot/models.py b/bot/models.py
index 8953afc..5d6fb1a 100644
--- a/bot/models.py
+++ b/bot/models.py
@@ -1,6 +1,9 @@
+import os
+
from peewee import Model, PrimaryKeyField, BigIntegerField, DateTimeField, IntegerField, CharField, SqliteDatabase
import config
+prev_state = os.path.isfile(config.DB_FILE)
db = SqliteDatabase(config.DB_FILE)
@@ -23,7 +26,7 @@ class Meta:
class Word(BaseModel):
id = PrimaryKeyField(unique=True)
- word = CharField()
+ word = CharField(max_length=100)
show_num = IntegerField()
vote_num = IntegerField()
From 8bd10eeec14aab60f2384310cd2674aefaf7ea7b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 19:12:46 +0300
Subject: [PATCH 24/62] feat: add creator of words objects
---
bot/services.py | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/bot/services.py b/bot/services.py
index 09367d8..ea03f2a 100644
--- a/bot/services.py
+++ b/bot/services.py
@@ -7,7 +7,7 @@ def _check_data_file(path: str):
exit(1)
-def get_words() -> list[str]:
+def _get_words() -> list[str]:
"""
Converts a txt file with words on separate lines to a list of strings.
Empty strings would be deleted
@@ -22,3 +22,18 @@ def get_words() -> list[str]:
words = list(filter(None, file_content)) # remove empty strings
return words
+
+
+def get_words_objects() -> list[dict]:
+ word_list = _get_words()
+ words = []
+
+ for index, _word in enumerate(word_list, start=1):
+ words.append({
+ 'id': index,
+ 'word': _word,
+ 'show_num': 0,
+ 'vote_num': 0
+ })
+
+ return words
From f841c3f9ca7dabe84b07f6318f668f3404aed701 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 19:20:37 +0300
Subject: [PATCH 25/62] feat: add words to the db func
---
bot/db.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index 84391e2..7954369 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,8 +1,10 @@
from models import models_list, db, Word, prev_state
+import services
def _add_words() -> None:
- pass
+ with db:
+ Word.insert_many(services.get_words_objects()).execute()
def create_tables():
From 576781276e5c9ce4d3f89339da9756284f0a2d8d Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 19:52:44 +0300
Subject: [PATCH 26/62] feat: add_user func
---
bot/db.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index 7954369..670fc16 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,4 +1,6 @@
-from models import models_list, db, Word, prev_state
+from datetime import datetime
+
+from models import models_list, db, Word, User, prev_state
import services
@@ -15,3 +17,14 @@ def create_tables():
if not has_db:
_add_words()
+
+
+def add_user(usr_id: int, usr_date: datetime):
+ usr_obj = User(
+ tg_id=usr_id,
+ date_reg=usr_date,
+ resp_num=0
+ )
+
+ with db:
+ usr_obj.save()
From 94a02a2208aa4a8edd377183c7d6a1533197584b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 19:53:03 +0300
Subject: [PATCH 27/62] fix: allow null value in User
---
bot/models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/models.py b/bot/models.py
index 5d6fb1a..adca93b 100644
--- a/bot/models.py
+++ b/bot/models.py
@@ -16,7 +16,7 @@ class User(BaseModel):
id = PrimaryKeyField(unique=True)
tg_id = BigIntegerField(unique=True)
date_reg = DateTimeField()
- date_act = DateTimeField()
+ date_act = DateTimeField(null=True)
resp_num = IntegerField()
class Meta:
From 01432f3a82cfc075d584f17be529c1e53cb25599 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 19:54:58 +0300
Subject: [PATCH 28/62] feat: remove stop command
---
bot/main.py | 5 -----
bot/messages.py | 2 --
2 files changed, 7 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 023235e..c89becd 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -39,11 +39,6 @@ async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
-@dp.message(Command("stop"))
-async def command_stop_handler(message: Message) -> None:
- await message.answer(messages.STOP)
-
-
@dp.message()
async def unknown_command_handler(message: Message) -> None:
await message.answer(messages.UNKNOWN)
diff --git a/bot/messages.py b/bot/messages.py
index ea8d5e2..ff59cfa 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -4,8 +4,6 @@
HELP = "Sample of /help message"
-STOP = "Sample of /stop message"
-
UNKNOWN = "Unknown command, type /help"
ON_START = "Бот запущен"
From cc4abbd1916fc47ad2d18ad07b53f2db4f10d696 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 20:13:10 +0300
Subject: [PATCH 29/62] feat(add_user): usr_date could be none
---
bot/db.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index 670fc16..572601e 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -19,7 +19,9 @@ def create_tables():
_add_words()
-def add_user(usr_id: int, usr_date: datetime):
+def add_user(usr_id: int, usr_date: datetime = None):
+ usr_date = usr_date if usr_date else datetime.now()
+
usr_obj = User(
tg_id=usr_id,
date_reg=usr_date,
From 1beebc77ebe711d61f5dfb7edae11b3a328175b7 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 20:28:11 +0300
Subject: [PATCH 30/62] feat: update user func
---
bot/db.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/bot/db.py b/bot/db.py
index 572601e..573f31d 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -30,3 +30,12 @@ def add_user(usr_id: int, usr_date: datetime = None):
with db:
usr_obj.save()
+
+
+def update_user(usr_id: int, usr_date: datetime = None):
+ usr = User.get(User.tg_id == usr_id)
+ usr.date_act = usr_date if usr_date else datetime.now()
+ usr.resp_num = usr.resp_num + 1
+
+ with db:
+ usr.save()
From d1d11a3becfae19ad94b6801f9ca1783c21f5eaa Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 20:44:52 +0300
Subject: [PATCH 31/62] feat: update words func
---
bot/db.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/bot/db.py b/bot/db.py
index 573f31d..cfed556 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -39,3 +39,16 @@ def update_user(usr_id: int, usr_date: datetime = None):
with db:
usr.save()
+
+
+def update_words(voted_word_id: int, linked_word_id: int):
+ voted_word = Word.get(Word.id == voted_word_id)
+ voted_word.show_num += 1
+ voted_word.vote_num += 1
+
+ linked_word = Word.get(Word.id == linked_word_id)
+ linked_word.show_num += 1
+
+ with db:
+ voted_word.save()
+ linked_word.save()
From fdd6e4f8c058bdb05450067d968514de4822d7d6 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 20:48:54 +0300
Subject: [PATCH 32/62] refactor(update_user): += instead of =
---
bot/db.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index cfed556..75a68de 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -35,7 +35,7 @@ def add_user(usr_id: int, usr_date: datetime = None):
def update_user(usr_id: int, usr_date: datetime = None):
usr = User.get(User.tg_id == usr_id)
usr.date_act = usr_date if usr_date else datetime.now()
- usr.resp_num = usr.resp_num + 1
+ usr.resp_num += 1
with db:
usr.save()
From f75d2c2f4385eb9005b1465d6cf91a1507aaf458 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 21:06:13 +0300
Subject: [PATCH 33/62] feat: get words func
---
bot/db.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/bot/db.py b/bot/db.py
index 75a68de..0131246 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,4 +1,5 @@
from datetime import datetime
+from typing import Tuple, Any
from models import models_list, db, Word, User, prev_state
import services
@@ -52,3 +53,9 @@ def update_words(voted_word_id: int, linked_word_id: int):
with db:
voted_word.save()
linked_word.save()
+
+
+def get_words(words_ids: tuple[int, int]) -> tuple[str]:
+ out = tuple(str(Word.get(Word.id == i).word) for i in words_ids)
+
+ return out
From 71cb179b8ff5b9ad827a22a34dabf16432b0b13d Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 21:16:42 +0300
Subject: [PATCH 34/62] refactor: change the show count when getting
---
bot/db.py | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/bot/db.py b/bot/db.py
index 0131246..66aef46 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,5 +1,4 @@
from datetime import datetime
-from typing import Tuple, Any
from models import models_list, db, Word, User, prev_state
import services
@@ -42,20 +41,27 @@ def update_user(usr_id: int, usr_date: datetime = None):
usr.save()
-def update_words(voted_word_id: int, linked_word_id: int):
+def _update_show_num(word_id: int) -> None:
+ word = Word.get(Word.id == word_id)
+ word.show_num += 1
+
+ with db:
+ word.save()
+
+
+def update_voted_word(voted_word_id: int):
voted_word = Word.get(Word.id == voted_word_id)
- voted_word.show_num += 1
voted_word.vote_num += 1
- linked_word = Word.get(Word.id == linked_word_id)
- linked_word.show_num += 1
-
with db:
voted_word.save()
- linked_word.save()
def get_words(words_ids: tuple[int, int]) -> tuple[str]:
- out = tuple(str(Word.get(Word.id == i).word) for i in words_ids)
+ out = []
+
+ for i in words_ids:
+ _update_show_num(i)
+ out.append(str(Word.get(Word.id == i).word))
- return out
+ return tuple(out)
From 7e437c87fb5fbdfde61f5843fadcc89f4eefe510 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Mon, 23 Oct 2023 21:30:28 +0300
Subject: [PATCH 35/62] feat: get max word id func
---
bot/db.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/bot/db.py b/bot/db.py
index 66aef46..ac32a44 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,5 +1,7 @@
from datetime import datetime
+from peewee import fn
+
from models import models_list, db, Word, User, prev_state
import services
@@ -65,3 +67,8 @@ def get_words(words_ids: tuple[int, int]) -> tuple[str]:
out.append(str(Word.get(Word.id == i).word))
return tuple(out)
+
+
+def get_max_word_id() -> int:
+ max_id = Word.select(fn.MAX(Word.id)).scalar()
+ return max_id
From e619a9c399509c2c20249ccd9ee11712e90f293b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Tue, 24 Oct 2023 10:47:32 +0300
Subject: [PATCH 36/62] feat: implement add user and create tables
---
bot/main.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/bot/main.py b/bot/main.py
index c89becd..b5f8fe4 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -9,6 +9,7 @@
import config
import messages
+import db
dp = Dispatcher()
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
@@ -16,7 +17,7 @@
@dp.startup()
async def on_startup():
- # await config.set_commands(dp)
+ db.create_tables()
await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_START)
@@ -31,6 +32,7 @@ async def command_start_handler(message: Message) -> None:
"""
This handler receives messages with `/start` command
"""
+ db.add_user(message.from_user.id, message.date)
await message.answer(messages.START)
From 90e2b0db0d631f996d1e50690c76f4350f340573 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sat, 28 Oct 2023 20:43:52 +0300
Subject: [PATCH 37/62] fix: check the existence of the user before adding
---
bot/db.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index ac32a44..f116bc1 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -30,8 +30,10 @@ def add_user(usr_id: int, usr_date: datetime = None):
resp_num=0
)
+ query = User.select().where(User.tg_id == usr_id)
with db:
- usr_obj.save()
+ if not query.exists():
+ usr_obj.save()
def update_user(usr_id: int, usr_date: datetime = None):
From f2f1665e798a1117c1556da4215acf6c25f89252 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sat, 28 Oct 2023 20:48:02 +0300
Subject: [PATCH 38/62] refactor: replace get_max_word_id
---
bot/db.py | 5 -----
bot/services.py | 8 ++++++++
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/bot/db.py b/bot/db.py
index f116bc1..fb912b6 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -69,8 +69,3 @@ def get_words(words_ids: tuple[int, int]) -> tuple[str]:
out.append(str(Word.get(Word.id == i).word))
return tuple(out)
-
-
-def get_max_word_id() -> int:
- max_id = Word.select(fn.MAX(Word.id)).scalar()
- return max_id
diff --git a/bot/services.py b/bot/services.py
index ea03f2a..4e00776 100644
--- a/bot/services.py
+++ b/bot/services.py
@@ -1,4 +1,7 @@
import os.path
+from peewee import fn
+
+from models import Word
def _check_data_file(path: str):
@@ -37,3 +40,8 @@ def get_words_objects() -> list[dict]:
})
return words
+
+
+def get_max_word_id() -> int:
+ max_id = Word.select(fn.MAX(Word.id)).scalar()
+ return max_id
From 5d4a498449c23a70cfb5065e1b7d0127e9cae1ba Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sat, 28 Oct 2023 21:29:03 +0300
Subject: [PATCH 39/62] feat: create get_words_ids
---
bot/services.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/bot/services.py b/bot/services.py
index 4e00776..5aa7a62 100644
--- a/bot/services.py
+++ b/bot/services.py
@@ -1,4 +1,6 @@
import os.path
+import random
+
from peewee import fn
from models import Word
@@ -42,6 +44,12 @@ def get_words_objects() -> list[dict]:
return words
-def get_max_word_id() -> int:
+def get_words_ids() -> tuple[int, int]:
max_id = Word.select(fn.MAX(Word.id)).scalar()
- return max_id
+ id_1 = id_2 = random.randint(1, max_id)
+
+ while id_1 == id_2:
+ id_2 = random.randint(1, max_id)
+
+ return id_1, id_2
+
From 51489b2a273f357a5a517244e2d61ee1b8ebdd16 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sat, 28 Oct 2023 23:15:49 +0300
Subject: [PATCH 40/62] feat: add test command
---
bot/main.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/bot/main.py b/bot/main.py
index b5f8fe4..e739218 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -10,6 +10,7 @@
import config
import messages
import db
+from bot import services
dp = Dispatcher()
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
@@ -41,6 +42,14 @@ async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
+@dp.message(Command("test"))
+async def command_help_handler(message: Message) -> None:
+ ids = services.get_words_ids()
+ out = db.get_words(ids)
+ db.update_user(message.from_user.id, message.date)
+ await message.answer(f'{out[0]}, {out[1]}')
+
+
@dp.message()
async def unknown_command_handler(message: Message) -> None:
await message.answer(messages.UNKNOWN)
From 01b4a9324f31fac55d56da93da733b815a5ef4c7 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sat, 28 Oct 2023 23:28:32 +0300
Subject: [PATCH 41/62] feat: add admin filter to the test command
---
bot/main.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/bot/main.py b/bot/main.py
index e739218..ea00c78 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -6,6 +6,7 @@
from aiogram.enums import ParseMode
from aiogram.filters import Command
from aiogram.types import Message
+from aiogram import F
import config
import messages
@@ -42,7 +43,7 @@ async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
-@dp.message(Command("test"))
+@dp.message((F.from_user.id == config.settings.admin_id) & (F.text == "test"))
async def command_help_handler(message: Message) -> None:
ids = services.get_words_ids()
out = db.get_words(ids)
From a121922d980a5aa834b1a1404cd35a792d06356b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 16:19:36 +0300
Subject: [PATCH 42/62] chore: add comment for future refactoring
---
bot/db.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/db.py b/bot/db.py
index fb912b6..1add65e 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -30,7 +30,7 @@ def add_user(usr_id: int, usr_date: datetime = None):
resp_num=0
)
- query = User.select().where(User.tg_id == usr_id)
+ query = User.select().where(User.tg_id == usr_id) # change to EAFP way
with db:
if not query.exists():
usr_obj.save()
From 87cb4ded8863fce310721ff4ba7d8e0048bbfd8e Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 16:31:58 +0300
Subject: [PATCH 43/62] feat: add on-start keyboard
---
bot/keyboards.py | 14 ++++++++++++++
bot/main.py | 3 ++-
bot/messages.py | 3 +++
3 files changed, 19 insertions(+), 1 deletion(-)
create mode 100644 bot/keyboards.py
diff --git a/bot/keyboards.py b/bot/keyboards.py
new file mode 100644
index 0000000..24c0461
--- /dev/null
+++ b/bot/keyboards.py
@@ -0,0 +1,14 @@
+from aiogram import types
+import messages
+
+kb_on_start = [
+ [
+ types.KeyboardButton(text=messages.KB_START_TEXT),
+ ],
+]
+
+keyboard_start = types.ReplyKeyboardMarkup(
+ keyboard=kb_on_start,
+ resize_keyboard=True,
+ input_field_placeholder=messages.KB_START_PH
+)
diff --git a/bot/main.py b/bot/main.py
index ea00c78..0cbb3df 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -12,6 +12,7 @@
import messages
import db
from bot import services
+from keyboards import keyboard_start
dp = Dispatcher()
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
@@ -35,7 +36,7 @@ async def command_start_handler(message: Message) -> None:
This handler receives messages with `/start` command
"""
db.add_user(message.from_user.id, message.date)
- await message.answer(messages.START)
+ await message.answer(messages.START, reply_markup=keyboard_start)
@dp.message(Command("help"))
diff --git a/bot/messages.py b/bot/messages.py
index ff59cfa..2cf4ffe 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -9,3 +9,6 @@
ON_START = "Бот запущен"
ON_STOP = "Бот остановлен"
+
+KB_START_PH = "Нажмите, если готовы начать"
+KB_START_TEXT = "Поехали!"
From 0eb949df87b7ad1d7001bfef68448c29daae72d3 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 16:41:37 +0300
Subject: [PATCH 44/62] feat: placeholder for voting handler
---
bot/main.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/bot/main.py b/bot/main.py
index 0cbb3df..c793f5e 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -39,6 +39,12 @@ async def command_start_handler(message: Message) -> None:
await message.answer(messages.START, reply_markup=keyboard_start)
+@dp.message(F.text == messages.KB_START_TEXT)
+async def with_puree(message: types.Message):
+ pass
+ await message.reply("pass", reply_markup=types.ReplyKeyboardRemove())
+
+
@dp.message(Command("help"))
async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
From cbf1aced1061b5c5d60bc7e3c9fccd2336be68a0 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 16:48:15 +0300
Subject: [PATCH 45/62] feat: add voting keyboard
---
bot/keyboards.py | 17 +++++++++++++++--
bot/main.py | 4 ++--
bot/messages.py | 2 ++
3 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/bot/keyboards.py b/bot/keyboards.py
index 24c0461..f61e538 100644
--- a/bot/keyboards.py
+++ b/bot/keyboards.py
@@ -1,14 +1,27 @@
from aiogram import types
import messages
-kb_on_start = [
+_kb_on_start = [
[
types.KeyboardButton(text=messages.KB_START_TEXT),
],
]
+_kb_on_voting = [
+ [
+ types.KeyboardButton(text="1"),
+ types.KeyboardButton(text="2"),
+ ],
+]
+
keyboard_start = types.ReplyKeyboardMarkup(
- keyboard=kb_on_start,
+ keyboard=_kb_on_start,
resize_keyboard=True,
input_field_placeholder=messages.KB_START_PH
)
+
+keyboard_voting = types.ReplyKeyboardMarkup(
+ keyboard=_kb_on_voting,
+ resize_keyboard=True,
+ input_field_placeholder=messages.KB_VOTING_PH
+)
diff --git a/bot/main.py b/bot/main.py
index c793f5e..f4ca465 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -12,7 +12,7 @@
import messages
import db
from bot import services
-from keyboards import keyboard_start
+from keyboards import keyboard_start, keyboard_voting
dp = Dispatcher()
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
@@ -42,7 +42,7 @@ async def command_start_handler(message: Message) -> None:
@dp.message(F.text == messages.KB_START_TEXT)
async def with_puree(message: types.Message):
pass
- await message.reply("pass", reply_markup=types.ReplyKeyboardRemove())
+ await message.reply("pass", reply_markup=keyboard_voting)
@dp.message(Command("help"))
diff --git a/bot/messages.py b/bot/messages.py
index 2cf4ffe..4d350b3 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -12,3 +12,5 @@
KB_START_PH = "Нажмите, если готовы начать"
KB_START_TEXT = "Поехали!"
+
+KB_VOTING_PH = "Выберите один из двух вариантов"
From 5a4a8c08eaaf5d833d431dd0f8aae99a55c41daa Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 16:57:55 +0300
Subject: [PATCH 46/62] feat: generate voting message
---
bot/main.py | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index f4ca465..47aa8af 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -7,6 +7,7 @@
from aiogram.filters import Command
from aiogram.types import Message
from aiogram import F
+from aiogram.utils.markdown import hbold
import config
import messages
@@ -18,6 +19,14 @@
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
+def _new_pair(message) -> str:
+ ids = services.get_words_ids()
+ out = db.get_words(ids)
+ db.update_user(message.from_user.id, message.date)
+ return f'1. {hbold(out[0])}\n\n' \
+ f'2. {hbold(out[1])}'
+
+
@dp.startup()
async def on_startup():
db.create_tables()
@@ -40,9 +49,9 @@ async def command_start_handler(message: Message) -> None:
@dp.message(F.text == messages.KB_START_TEXT)
-async def with_puree(message: types.Message):
- pass
- await message.reply("pass", reply_markup=keyboard_voting)
+async def voting_message_handler(message: types.Message):
+ ans = _new_pair(message)
+ await message.answer(ans, reply_markup=keyboard_voting)
@dp.message(Command("help"))
@@ -52,10 +61,7 @@ async def command_help_handler(message: Message) -> None:
@dp.message((F.from_user.id == config.settings.admin_id) & (F.text == "test"))
async def command_help_handler(message: Message) -> None:
- ids = services.get_words_ids()
- out = db.get_words(ids)
- db.update_user(message.from_user.id, message.date)
- await message.answer(f'{out[0]}, {out[1]}')
+ await message.answer(_new_pair(message))
@dp.message()
From 7a5ee235dac38da87d29fdebc191ed94b7b84901 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 17:15:15 +0300
Subject: [PATCH 47/62] chore: comments with todo
---
bot/main.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/bot/main.py b/bot/main.py
index 47aa8af..9a394f5 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -50,10 +50,18 @@ async def command_start_handler(message: Message) -> None:
@dp.message(F.text == messages.KB_START_TEXT)
async def voting_message_handler(message: types.Message):
+ # todo сохраняем в фсм два айдишника
ans = _new_pair(message)
await message.answer(ans, reply_markup=keyboard_voting)
+# todo хэндлер для цифровой команды
+# todo определяем номер цифры
+# todo голосуем за айдишник под указанным номером
+# todo сбрасываем фсм
+# todo показываем новое сообщение
+# todo сохраняем в фсм их айдишники
+
@dp.message(Command("help"))
async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
From 21a6190a5a370667a43798654f81e5d8ab05b4af Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 20:16:47 +0300
Subject: [PATCH 48/62] feat: remove a first step in polling
---
bot/keyboards.py | 12 ------------
bot/main.py | 8 ++------
bot/messages.py | 3 ---
3 files changed, 2 insertions(+), 21 deletions(-)
diff --git a/bot/keyboards.py b/bot/keyboards.py
index f61e538..3521526 100644
--- a/bot/keyboards.py
+++ b/bot/keyboards.py
@@ -1,12 +1,6 @@
from aiogram import types
import messages
-_kb_on_start = [
- [
- types.KeyboardButton(text=messages.KB_START_TEXT),
- ],
-]
-
_kb_on_voting = [
[
types.KeyboardButton(text="1"),
@@ -14,12 +8,6 @@
],
]
-keyboard_start = types.ReplyKeyboardMarkup(
- keyboard=_kb_on_start,
- resize_keyboard=True,
- input_field_placeholder=messages.KB_START_PH
-)
-
keyboard_voting = types.ReplyKeyboardMarkup(
keyboard=_kb_on_voting,
resize_keyboard=True,
diff --git a/bot/main.py b/bot/main.py
index 9a394f5..c7ebbda 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -13,7 +13,7 @@
import messages
import db
from bot import services
-from keyboards import keyboard_start, keyboard_voting
+from keyboards import keyboard_voting
dp = Dispatcher()
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
@@ -45,12 +45,8 @@ async def command_start_handler(message: Message) -> None:
This handler receives messages with `/start` command
"""
db.add_user(message.from_user.id, message.date)
- await message.answer(messages.START, reply_markup=keyboard_start)
+ await message.answer(messages.START)
-
-@dp.message(F.text == messages.KB_START_TEXT)
-async def voting_message_handler(message: types.Message):
- # todo сохраняем в фсм два айдишника
ans = _new_pair(message)
await message.answer(ans, reply_markup=keyboard_voting)
diff --git a/bot/messages.py b/bot/messages.py
index 4d350b3..394ce4d 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -10,7 +10,4 @@
ON_STOP = "Бот остановлен"
-KB_START_PH = "Нажмите, если готовы начать"
-KB_START_TEXT = "Поехали!"
-
KB_VOTING_PH = "Выберите один из двух вариантов"
From 56e369a4bffed9576be78ccc9ba85c23f99614c2 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 20:23:04 +0300
Subject: [PATCH 49/62] feat: add handler for polling
---
bot/main.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/bot/main.py b/bot/main.py
index c7ebbda..1df48f8 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -51,7 +51,12 @@ async def command_start_handler(message: Message) -> None:
await message.answer(ans, reply_markup=keyboard_voting)
-# todo хэндлер для цифровой команды
+@dp.message((F.text == "1") | (F.text == "2"))
+async def polling_handler(message: Message) -> None:
+ ans = _new_pair(message)
+ await message.answer(ans, reply_markup=keyboard_voting)
+
+
# todo определяем номер цифры
# todo голосуем за айдишник под указанным номером
# todo сбрасываем фсм
From e8524f33256d6a127b8014849661fb7cb0b90e6b Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 20:25:23 +0300
Subject: [PATCH 50/62] refactor: more DRY practice
---
bot/main.py | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 1df48f8..91b86d1 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -39,18 +39,6 @@ async def on_shutdown():
await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_STOP)
-@dp.message(Command("start"))
-async def command_start_handler(message: Message) -> None:
- """
- This handler receives messages with `/start` command
- """
- db.add_user(message.from_user.id, message.date)
- await message.answer(messages.START)
-
- ans = _new_pair(message)
- await message.answer(ans, reply_markup=keyboard_voting)
-
-
@dp.message((F.text == "1") | (F.text == "2"))
async def polling_handler(message: Message) -> None:
ans = _new_pair(message)
@@ -63,6 +51,17 @@ async def polling_handler(message: Message) -> None:
# todo показываем новое сообщение
# todo сохраняем в фсм их айдишники
+@dp.message(Command("start"))
+async def command_start_handler(message: Message) -> None:
+ """
+ This handler receives messages with `/start` command
+ """
+ db.add_user(message.from_user.id, message.date)
+ await message.answer(messages.START)
+
+ await polling_handler(message)
+
+
@dp.message(Command("help"))
async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
From 61a8c4c73bcf0a89c1cf696a736a084c7bbdf3f6 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 20:59:29 +0300
Subject: [PATCH 51/62] feat: endless polling
---
bot/main.py | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 91b86d1..6cdb02f 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -5,6 +5,8 @@
from aiogram import Bot, Dispatcher, Router, types
from aiogram.enums import ParseMode
from aiogram.filters import Command
+from aiogram.fsm.context import FSMContext
+from aiogram.fsm.state import StatesGroup, State
from aiogram.types import Message
from aiogram import F
from aiogram.utils.markdown import hbold
@@ -19,8 +21,11 @@
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
-def _new_pair(message) -> str:
- ids = services.get_words_ids()
+class Answer(StatesGroup):
+ prev_id = State()
+
+
+def _new_pair(message, ids) -> str:
out = db.get_words(ids)
db.update_user(message.from_user.id, message.date)
return f'1. {hbold(out[0])}\n\n' \
@@ -40,16 +45,15 @@ async def on_shutdown():
@dp.message((F.text == "1") | (F.text == "2"))
-async def polling_handler(message: Message) -> None:
- ans = _new_pair(message)
- await message.answer(ans, reply_markup=keyboard_voting)
+async def polling_handler(message: Message, state: FSMContext) -> None:
+ data = await state.get_data()
+ # todo парсить, определить за кого голос и увеличить
+ ids = services.get_words_ids()
+ await state.update_data(prev_id=f'{ids[0]}|{ids[1]}')
+ ans = _new_pair(message, ids)
+ await message.answer(f"{ans}\n{ids}\n{data}", reply_markup=keyboard_voting)
-# todo определяем номер цифры
-# todo голосуем за айдишник под указанным номером
-# todo сбрасываем фсм
-# todo показываем новое сообщение
-# todo сохраняем в фсм их айдишники
@dp.message(Command("start"))
async def command_start_handler(message: Message) -> None:
From 5663b9abbdd986643f378b01cd03bef167138747 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 21:28:49 +0300
Subject: [PATCH 52/62] fix: return the first step of polling
---
bot/keyboards.py | 12 ++++++++++++
bot/main.py | 8 +++-----
bot/messages.py | 3 +++
3 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/bot/keyboards.py b/bot/keyboards.py
index 3521526..3b74dc0 100644
--- a/bot/keyboards.py
+++ b/bot/keyboards.py
@@ -1,6 +1,18 @@
from aiogram import types
import messages
+_kb_on_start = [
+ [
+ types.KeyboardButton(text=messages.KB_START_TEXT),
+ ],
+]
+
+keyboard_start = types.ReplyKeyboardMarkup(
+ keyboard=_kb_on_start,
+ resize_keyboard=True,
+ input_field_placeholder=messages.KB_START_PH
+)
+
_kb_on_voting = [
[
types.KeyboardButton(text="1"),
diff --git a/bot/main.py b/bot/main.py
index 6cdb02f..48526f0 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -15,7 +15,7 @@
import messages
import db
from bot import services
-from keyboards import keyboard_voting
+from keyboards import keyboard_voting, keyboard_start
dp = Dispatcher()
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
@@ -44,7 +44,7 @@ async def on_shutdown():
await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_STOP)
-@dp.message((F.text == "1") | (F.text == "2"))
+@dp.message((F.text == "1") | (F.text == "2") | (F.text == "Поехали!"))
async def polling_handler(message: Message, state: FSMContext) -> None:
data = await state.get_data()
# todo парсить, определить за кого голос и увеличить
@@ -61,9 +61,7 @@ async def command_start_handler(message: Message) -> None:
This handler receives messages with `/start` command
"""
db.add_user(message.from_user.id, message.date)
- await message.answer(messages.START)
-
- await polling_handler(message)
+ await message.answer(messages.START, reply_markup=keyboard_start)
@dp.message(Command("help"))
diff --git a/bot/messages.py b/bot/messages.py
index 394ce4d..7db7014 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -11,3 +11,6 @@
ON_STOP = "Бот остановлен"
KB_VOTING_PH = "Выберите один из двух вариантов"
+
+KB_START_PH = "Нажмите, если готовы начать"
+KB_START_TEXT = "Поехали!"
From 2784006cd872f33ca4b6424e6824f062f61c4a59 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 22:00:01 +0300
Subject: [PATCH 53/62] feat: add polling
---
bot/main.py | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 48526f0..35abbc3 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -32,6 +32,18 @@ def _new_pair(message, ids) -> str:
f'2. {hbold(out[1])}'
+def _get_usr_ans(message: Message, data: dict) -> int:
+ try:
+ ans = data['prev_id'].split('|')
+ except KeyError:
+ ans = []
+
+ if message.text.isdecimal() and ans:
+ index = int(message.text) - 1
+ return ans[index]
+ return 0
+
+
@dp.startup()
async def on_startup():
db.create_tables()
@@ -47,12 +59,14 @@ async def on_shutdown():
@dp.message((F.text == "1") | (F.text == "2") | (F.text == "Поехали!"))
async def polling_handler(message: Message, state: FSMContext) -> None:
data = await state.get_data()
- # todo парсить, определить за кого голос и увеличить
+
+ voted_id = _get_usr_ans(message, data)
+ db.update_voted_word(voted_id)
ids = services.get_words_ids()
await state.update_data(prev_id=f'{ids[0]}|{ids[1]}')
ans = _new_pair(message, ids)
- await message.answer(f"{ans}\n{ids}\n{data}", reply_markup=keyboard_voting)
+ await message.answer(ans, reply_markup=keyboard_voting)
@dp.message(Command("start"))
From a0d7f2643764eb8f39f4462e90a6298c6c1fbe46 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 22:00:47 +0300
Subject: [PATCH 54/62] fix: the word id cannot be zero
---
bot/db.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/bot/db.py b/bot/db.py
index 1add65e..0773ff3 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -54,6 +54,9 @@ def _update_show_num(word_id: int) -> None:
def update_voted_word(voted_word_id: int):
+ if not voted_word_id:
+ return
+
voted_word = Word.get(Word.id == voted_word_id)
voted_word.vote_num += 1
From e5756b8cd2611ef67155553534dc3ca3f320b1e7 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 22:12:59 +0300
Subject: [PATCH 55/62] refactor: refactor test handler
---
bot/main.py | 5 -----
1 file changed, 5 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 35abbc3..4715b8d 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -83,11 +83,6 @@ async def command_help_handler(message: Message) -> None:
await message.answer(messages.HELP)
-@dp.message((F.from_user.id == config.settings.admin_id) & (F.text == "test"))
-async def command_help_handler(message: Message) -> None:
- await message.answer(_new_pair(message))
-
-
@dp.message()
async def unknown_command_handler(message: Message) -> None:
await message.answer(messages.UNKNOWN)
From 0e139e654c168d93be3a2c9081d07ed7188b0913 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 22:18:05 +0300
Subject: [PATCH 56/62] refactor: improve type hints
---
bot/config.py | 2 +-
bot/db.py | 10 ++++------
bot/main.py | 2 +-
bot/services.py | 3 +--
4 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/bot/config.py b/bot/config.py
index c8717aa..c413ef4 100644
--- a/bot/config.py
+++ b/bot/config.py
@@ -10,7 +10,7 @@ class Bot:
admin_id: int
-def _get_settings():
+def _get_settings() -> Bot:
load_dotenv()
return Bot(bot_token=os.getenv("BOT_TOKEN"),
admin_id=int(os.getenv("ADMIN_ID")))
diff --git a/bot/db.py b/bot/db.py
index 0773ff3..633c7ed 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,7 +1,5 @@
from datetime import datetime
-from peewee import fn
-
from models import models_list, db, Word, User, prev_state
import services
@@ -11,7 +9,7 @@ def _add_words() -> None:
Word.insert_many(services.get_words_objects()).execute()
-def create_tables():
+def create_tables() -> None:
has_db = prev_state
with db:
@@ -21,7 +19,7 @@ def create_tables():
_add_words()
-def add_user(usr_id: int, usr_date: datetime = None):
+def add_user(usr_id: int, usr_date: datetime = None) -> None:
usr_date = usr_date if usr_date else datetime.now()
usr_obj = User(
@@ -36,7 +34,7 @@ def add_user(usr_id: int, usr_date: datetime = None):
usr_obj.save()
-def update_user(usr_id: int, usr_date: datetime = None):
+def update_user(usr_id: int, usr_date: datetime = None) -> None:
usr = User.get(User.tg_id == usr_id)
usr.date_act = usr_date if usr_date else datetime.now()
usr.resp_num += 1
@@ -53,7 +51,7 @@ def _update_show_num(word_id: int) -> None:
word.save()
-def update_voted_word(voted_word_id: int):
+def update_voted_word(voted_word_id: int) -> None:
if not voted_word_id:
return
diff --git a/bot/main.py b/bot/main.py
index 4715b8d..05da80a 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -25,7 +25,7 @@ class Answer(StatesGroup):
prev_id = State()
-def _new_pair(message, ids) -> str:
+def _new_pair(message: Message, ids: tuple[int, int]) -> str:
out = db.get_words(ids)
db.update_user(message.from_user.id, message.date)
return f'1. {hbold(out[0])}\n\n' \
diff --git a/bot/services.py b/bot/services.py
index 5aa7a62..f52b3c9 100644
--- a/bot/services.py
+++ b/bot/services.py
@@ -6,7 +6,7 @@
from models import Word
-def _check_data_file(path: str):
+def _check_data_file(path: str) -> None:
if not os.path.isfile(path):
print("Can't read data file with words")
exit(1)
@@ -52,4 +52,3 @@ def get_words_ids() -> tuple[int, int]:
id_2 = random.randint(1, max_id)
return id_1, id_2
-
From e52112c867ad85a1ba24ba056a7fedfa4c89fc8e Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 22:36:03 +0300
Subject: [PATCH 57/62] fix: update words show num after voting
---
bot/db.py | 3 +--
bot/main.py | 19 +++++++++++++++----
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/bot/db.py b/bot/db.py
index 633c7ed..cbd2c48 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -43,7 +43,7 @@ def update_user(usr_id: int, usr_date: datetime = None) -> None:
usr.save()
-def _update_show_num(word_id: int) -> None:
+def update_show_num(word_id: int) -> None:
word = Word.get(Word.id == word_id)
word.show_num += 1
@@ -66,7 +66,6 @@ def get_words(words_ids: tuple[int, int]) -> tuple[str]:
out = []
for i in words_ids:
- _update_show_num(i)
out.append(str(Word.get(Word.id == i).word))
return tuple(out)
diff --git a/bot/main.py b/bot/main.py
index 05da80a..8a76af4 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -32,18 +32,29 @@ def _new_pair(message: Message, ids: tuple[int, int]) -> str:
f'2. {hbold(out[1])}'
-def _get_usr_ans(message: Message, data: dict) -> int:
+def _parse_state_data(data: dict) -> list[int] | list:
try:
ans = data['prev_id'].split('|')
+ return [int(i) for i in ans]
except KeyError:
- ans = []
+ return []
+
+def _get_usr_ans(message: Message, ans: list[int]) -> int:
if message.text.isdecimal() and ans:
index = int(message.text) - 1
return ans[index]
return 0
+def _upd_show_nums(ids: list[int, int]) -> None:
+ if not ids:
+ return
+
+ for i in ids:
+ db.update_show_num(i)
+
+
@dp.startup()
async def on_startup():
db.create_tables()
@@ -58,8 +69,8 @@ async def on_shutdown():
@dp.message((F.text == "1") | (F.text == "2") | (F.text == "Поехали!"))
async def polling_handler(message: Message, state: FSMContext) -> None:
- data = await state.get_data()
-
+ data = _parse_state_data(await state.get_data())
+ _upd_show_nums(data)
voted_id = _get_usr_ans(message, data)
db.update_voted_word(voted_id)
From 219e4d479d86fd4ff893883c7a2a67bc7701bd47 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 22:43:29 +0300
Subject: [PATCH 58/62] fix: update usr resp num after voting
---
bot/main.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index 8a76af4..b06aaac 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -25,9 +25,8 @@ class Answer(StatesGroup):
prev_id = State()
-def _new_pair(message: Message, ids: tuple[int, int]) -> str:
+def _new_pair(ids: tuple[int, int]) -> str:
out = db.get_words(ids)
- db.update_user(message.from_user.id, message.date)
return f'1. {hbold(out[0])}\n\n' \
f'2. {hbold(out[1])}'
@@ -47,12 +46,13 @@ def _get_usr_ans(message: Message, ans: list[int]) -> int:
return 0
-def _upd_show_nums(ids: list[int, int]) -> None:
+def _update_counters(message: Message, ids: list[int, int]) -> None:
if not ids:
return
for i in ids:
db.update_show_num(i)
+ db.update_user(message.from_user.id, message.date)
@dp.startup()
@@ -70,13 +70,13 @@ async def on_shutdown():
@dp.message((F.text == "1") | (F.text == "2") | (F.text == "Поехали!"))
async def polling_handler(message: Message, state: FSMContext) -> None:
data = _parse_state_data(await state.get_data())
- _upd_show_nums(data)
+ _update_counters(message, data)
voted_id = _get_usr_ans(message, data)
db.update_voted_word(voted_id)
ids = services.get_words_ids()
await state.update_data(prev_id=f'{ids[0]}|{ids[1]}')
- ans = _new_pair(message, ids)
+ ans = _new_pair(ids)
await message.answer(ans, reply_markup=keyboard_voting)
From 7c516235aed61053db2399f066614e72b13c82fc Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 23:06:15 +0300
Subject: [PATCH 59/62] chore: replace messages with templates
---
bot/main.py | 2 +-
bot/messages.py | 17 +++++++++--------
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/bot/main.py b/bot/main.py
index b06aaac..ac61fce 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -67,7 +67,7 @@ async def on_shutdown():
await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_STOP)
-@dp.message((F.text == "1") | (F.text == "2") | (F.text == "Поехали!"))
+@dp.message((F.text == "1") | (F.text == "2") | (F.text == messages.KB_START_TEXT))
async def polling_handler(message: Message, state: FSMContext) -> None:
data = _parse_state_data(await state.get_data())
_update_counters(message, data)
diff --git a/bot/messages.py b/bot/messages.py
index 7db7014..cef5438 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -1,16 +1,17 @@
from aiogram.utils.markdown import hbold
-START = f"{hbold('Hello!')}"
+START = f"""{hbold('Привет!')}\n
+Это текст команды, которая отобразится на старте"""
-HELP = "Sample of /help message"
+HELP = "Текст для команды help"
-UNKNOWN = "Unknown command, type /help"
+UNKNOWN = "Текст, который отобразится, если пользователь отправил неожидаемое сообщение"
-ON_START = "Бот запущен"
+ON_START = "Бот запущен (будет отправлено админу)"
-ON_STOP = "Бот остановлен"
+ON_STOP = "Бот остановлен (будет отправлено админу)"
-KB_VOTING_PH = "Выберите один из двух вариантов"
+KB_VOTING_PH = "Плэйсхолдер для выбора ответа"
-KB_START_PH = "Нажмите, если готовы начать"
-KB_START_TEXT = "Поехали!"
+KB_START_PH = "Плэйсхолдер для старта бота"
+KB_START_TEXT = "Сообщение для старта бота"
From ed1e145556843436722ab9827596935a1f7cbf14 Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 23:12:31 +0300
Subject: [PATCH 60/62] feat: add voting msg title
---
bot/main.py | 3 ++-
bot/messages.py | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/bot/main.py b/bot/main.py
index ac61fce..a86f208 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -27,7 +27,8 @@ class Answer(StatesGroup):
def _new_pair(ids: tuple[int, int]) -> str:
out = db.get_words(ids)
- return f'1. {hbold(out[0])}\n\n' \
+ return f'{messages.VOTING_TITLE}\n' \
+ f'1. {hbold(out[0])}\n\n' \
f'2. {hbold(out[1])}'
diff --git a/bot/messages.py b/bot/messages.py
index cef5438..cfdfff4 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -15,3 +15,5 @@
KB_START_PH = "Плэйсхолдер для старта бота"
KB_START_TEXT = "Сообщение для старта бота"
+
+VOTING_TITLE = "Текст, который будет показан перед двумя вариантами в голосовании"
From e12e26e572e58ba3b13b35668dd8ec1d412255ed Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 23:23:32 +0300
Subject: [PATCH 61/62] feat: add tpl for a command list
---
bot/main.py | 18 +++++++++++++++++-
bot/messages.py | 4 ++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/bot/main.py b/bot/main.py
index a86f208..9f5b250 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -7,7 +7,7 @@
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup, State
-from aiogram.types import Message
+from aiogram.types import Message, BotCommand, BotCommandScopeDefault
from aiogram import F
from aiogram.utils.markdown import hbold
@@ -21,6 +21,21 @@
bot = Bot(config.settings.bot_token, parse_mode=ParseMode.HTML)
+async def _set_commands(target: Bot):
+ commands = [
+ BotCommand(
+ command='start',
+ description=messages.COMMAND_START
+ ),
+ BotCommand(
+ command='help',
+ description=messages.COMMAND_HELP
+ )
+ ]
+
+ await target.set_my_commands(commands, BotCommandScopeDefault())
+
+
class Answer(StatesGroup):
prev_id = State()
@@ -59,6 +74,7 @@ def _update_counters(message: Message, ids: list[int, int]) -> None:
@dp.startup()
async def on_startup():
db.create_tables()
+ await _set_commands(bot)
await bot.send_message(chat_id=config.settings.admin_id, text=messages.ON_START)
diff --git a/bot/messages.py b/bot/messages.py
index cfdfff4..bc7e588 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -17,3 +17,7 @@
KB_START_TEXT = "Сообщение для старта бота"
VOTING_TITLE = "Текст, который будет показан перед двумя вариантами в голосовании"
+
+COMMAND_START = "Описание команды /start"
+COMMAND_HELP = "Описание команды /help"
+
From 37d35725c2bc4cb237691d1606e5de376af9a86e Mon Sep 17 00:00:00 2001
From: Dan Sazonov
Date: Sun, 29 Oct 2023 23:26:28 +0300
Subject: [PATCH 62/62] chore: reformat code
---
bot/db.py | 2 +-
bot/keyboards.py | 1 +
bot/main.py | 8 ++++----
bot/messages.py | 1 -
bot/models.py | 1 +
5 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/bot/db.py b/bot/db.py
index cbd2c48..1a16495 100644
--- a/bot/db.py
+++ b/bot/db.py
@@ -1,7 +1,7 @@
from datetime import datetime
-from models import models_list, db, Word, User, prev_state
import services
+from models import models_list, db, Word, User, prev_state
def _add_words() -> None:
diff --git a/bot/keyboards.py b/bot/keyboards.py
index 3b74dc0..ead854e 100644
--- a/bot/keyboards.py
+++ b/bot/keyboards.py
@@ -1,4 +1,5 @@
from aiogram import types
+
import messages
_kb_on_start = [
diff --git a/bot/main.py b/bot/main.py
index 9f5b250..7475e28 100644
--- a/bot/main.py
+++ b/bot/main.py
@@ -2,19 +2,19 @@
import logging
import sys
-from aiogram import Bot, Dispatcher, Router, types
+from aiogram import Bot, Dispatcher
+from aiogram import F
from aiogram.enums import ParseMode
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup, State
from aiogram.types import Message, BotCommand, BotCommandScopeDefault
-from aiogram import F
from aiogram.utils.markdown import hbold
import config
-import messages
import db
-from bot import services
+import messages
+import services
from keyboards import keyboard_voting, keyboard_start
dp = Dispatcher()
diff --git a/bot/messages.py b/bot/messages.py
index bc7e588..77604a2 100644
--- a/bot/messages.py
+++ b/bot/messages.py
@@ -20,4 +20,3 @@
COMMAND_START = "Описание команды /start"
COMMAND_HELP = "Описание команды /help"
-
diff --git a/bot/models.py b/bot/models.py
index adca93b..5791db1 100644
--- a/bot/models.py
+++ b/bot/models.py
@@ -1,6 +1,7 @@
import os
from peewee import Model, PrimaryKeyField, BigIntegerField, DateTimeField, IntegerField, CharField, SqliteDatabase
+
import config
prev_state = os.path.isfile(config.DB_FILE)