diff --git a/.github/workflows/cicd-app.yml b/.github/workflows/cicd-app.yml index 62f39dd97..a2fcad99d 100644 --- a/.github/workflows/cicd-app.yml +++ b/.github/workflows/cicd-app.yml @@ -31,7 +31,7 @@ jobs: run: echo "ENV_PROFILE=dev" >> $GITHUB_ENV - name: Set ENV profile as PROD when it is a release - if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') + if: startsWith(github.ref, 'refs/tags/v1') || startsWith(github.ref, 'refs/heads/v1') || startsWith(github.ref, 'refs/tags/v2') || startsWith(github.ref, 'refs/heads/v2') run: echo "ENV_PROFILE=prod" >> $GITHUB_ENV - id: env_profile @@ -54,27 +54,40 @@ jobs: echo $VERSION echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - build: - name: Run unit tests, build and package + unit_test_backend: + name: Run backend unit tests needs: version runs-on: ubuntu-22.04 env: ENV_PROFILE: ${{needs.version.outputs.ENV_PROFILE}} VERSION: ${{needs.version.outputs.VERSION}} ACTIONS_ALLOW_UNSECURE_COMMANDS: true - PUPPETEER_SKIP_DOWNLOAD: "true" steps: - name: Show version run: echo "VERSION:${{ env.VERSION }} ENV_PROFILE:${{ env.ENV_PROFILE }}" - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Java JDK uses: actions/setup-java@v4.2.1 with: + # https://github.com/actions/setup-java/blob/main/README.md#Supported-distributions + distribution: zulu java-version: 17 - distribution: "dragonwell" + + - name: Checkout + uses: actions/checkout@v4 + + - name: Unit test + run: make test-back + + unit_test_frontend: + name: Run frontend unit tests + needs: version + runs-on: ubuntu-22.04 + env: + PUPPETEER_SKIP_DOWNLOAD: "true" + steps: + - name: Checkout + uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 @@ -96,7 +109,27 @@ jobs: working-directory: ./frontend - name: Unit test - run: make test + run: npm run test:unit --coverage + working-directory: ./frontend + + - name: Upload coverage + run: npx codecov + working-directory: ./frontend + + build: + name: Build and package + needs: version + runs-on: ubuntu-22.04 + env: + ENV_PROFILE: ${{needs.version.outputs.ENV_PROFILE}} + VERSION: ${{needs.version.outputs.VERSION}} + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + steps: + - name: Show version + run: echo "VERSION:${{ env.VERSION }} ENV_PROFILE:${{ env.ENV_PROFILE }}" + + - name: Checkout + uses: actions/checkout@v4 - name: Set up Docker Buildx id: buildx @@ -257,8 +290,8 @@ jobs: push_to_registry: name: Push to registry - needs: [version, e2e_test] - # needs: [version, e2e_test, e2e_multi_windows_test] + needs: [version, unit_test_backend, unit_test_frontend, e2e_test] + # needs: [version, e2e_test, e2e_multi_windows_test, unit_test_frontend, e2e_test] runs-on: ubuntu-22.04 if: startsWith(github.ref, 'refs/heads/dependabot') == false env: diff --git a/.github/workflows/cicd-datapipeline.yml b/.github/workflows/cicd-datapipeline.yml index 0ae2be4cb..5e792ef84 100644 --- a/.github/workflows/cicd-datapipeline.yml +++ b/.github/workflows/cicd-datapipeline.yml @@ -31,7 +31,7 @@ jobs: repository: mtes-mct/monitorenv - name: Set ENV_PROFILE as PROD when it is a release - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v1') || startsWith(github.ref, 'refs/heads/v1') || startsWith(github.ref, 'refs/tags/v2') || startsWith(github.ref, 'refs/heads/v2') run: echo "ENV_PROFILE=prod" >> $GITHUB_ENV - name: Set VERSION diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/ErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/ErrorCode.kt deleted file mode 100644 index 03cdb8c2c..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/ErrorCode.kt +++ /dev/null @@ -1,16 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.entities - -// Don't forget to mirror any update here in the frontend enum. -enum class ErrorCode { - /** - * Thrown when attempting to attach a mission to a reporting that has already a mission - * attached. - */ - CHILD_ALREADY_ATTACHED, - - /** Thrown when attempting to archive an entity linked to non-archived child(ren). */ - UNARCHIVED_CHILD, - - /** Thrown when attempting to delete a mission that has actions created by other applications. */ - EXISTING_MISSION_ACTION, -} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/controlUnit/ControlUnitContactEntity.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/controlUnit/ControlUnitContactEntity.kt index 90f7aa467..48682faac 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/controlUnit/ControlUnitContactEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/controlUnit/ControlUnitContactEntity.kt @@ -4,6 +4,8 @@ data class ControlUnitContactEntity( val id: Int? = null, val controlUnitId: Int, val email: String? = null, + val isEmailSubscriptionContact: Boolean, + val isSmsSubscriptionContact: Boolean, val name: String, val phone: String? = null, ) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendInternalException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendInternalException.kt new file mode 100644 index 000000000..3cf728a94 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendInternalException.kt @@ -0,0 +1,25 @@ +package fr.gouv.cacem.monitorenv.domain.exceptions + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +/** + * Domain exception to throw when an internal error occurred in the backend. + * + * ## Examples + * - An unexpected exception has been caught. + * + * ## Logging + * This exception is logged as an error on the Backend side. + */ +open class BackendInternalException( + final override val message: String, + val originalException: Exception? = null, +) : Throwable(message) { + private val logger: Logger = LoggerFactory.getLogger(BackendInternalException::class.java) + + init { + logger.error("BackendInternalException: $message") + originalException?.let { logger.error("${it::class.simpleName}: ${it.message}") } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageErrorCode.kt new file mode 100644 index 000000000..fd72f859b --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageErrorCode.kt @@ -0,0 +1,32 @@ +package fr.gouv.cacem.monitorenv.domain.exceptions + +/** + * Error code thrown when the request is valid but the backend cannot process it. + * + * It's called "usage" because this request likely comes from an end-user action that's no longer valid + * which happens when their client data is not up-to-date with the backend. + * + * But it can also be a Frontend side bug. + * + * ## Examples + * - A user tries to create a resource that has already been created. + * - A user tries to delete a resource that doesn't exist anymore. + * + * ## Logging + * The related exception is NOT logged on the Backend side. + * It should be logged on the Frontend side IF it's unexpected (= Frontend bug), + * it should rather display a comprehensible error message to the end-user. + * + * ### ⚠️ Important + * **Don't forget to mirror any update here in the corresponding Frontend enum.** + */ +enum class BackendUsageErrorCode { + /** Thrown when attempting to attach a mission to a reporting that has already a mission attached. */ + CHILD_ALREADY_ATTACHED, + + /** Thrown when attempting to archive an entity linked to non-archived child(ren). */ + UNARCHIVED_CHILD, + + /** Thrown when attempting to delete a mission that has actions created by other applications. */ + EXISTING_MISSION_ACTION, +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt index 473d03219..edfc6cd5f 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt @@ -1,5 +1,24 @@ package fr.gouv.cacem.monitorenv.domain.exceptions -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode - -class BackendUsageException(val code: ErrorCode, val data: Any) : Throwable(code.name) +/** + * Domain exception to throw when a request is valid but the backend cannot process it. + * + * It's called "usage" because this request likely comes from an end-user action that's no longer valid + * which happens when their client data is not up-to-date with the backend. + * + * But it can also be a Frontend side bug. + * + * ## Examples + * - A user tries to create a resource that has already been created. + * - A user tries to delete a resource that doesn't exist anymore. + * + * ## Logging + * This exception is NOT logged on the Backend side. + * It should be logged on the Frontend side IF it's unexpected (= Frontend bug), + * it should rather display a comprehensible error message to the end-user. + */ +open class BackendUsageException( + val code: BackendUsageErrorCode, + final override val message: String? = null, + val data: Any? = null, +) : Throwable(code.name) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CodeNotFoundException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CodeNotFoundException.kt deleted file mode 100644 index aecfcbda8..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CodeNotFoundException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.exceptions - -class CodeNotFoundException(message: String, cause: Throwable? = null) : - Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt deleted file mode 100644 index b759b54c7..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.exceptions - -class CouldNotArchiveException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt index 6d5c9ef2d..d05bcfb15 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt @@ -1,3 +1,4 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendUsageException` instead.") class CouldNotDeleteException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotUpdateMissionException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotUpdateMissionException.kt deleted file mode 100644 index 503b6c4f5..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotUpdateMissionException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.exceptions - -class CouldNotUpdateMissionException(message: String, cause: Throwable? = null) : - Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt index aa666d45f..bcf4e5b55 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendUsageException` with the right code instead (depending on the case).") class EntityConversionException(message: String, cause: Throwable? = null) : Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt index 5e29386f5..2cfcc770b 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendUsageException` instead.") class NotFoundException(message: String, cause: Throwable? = null) : Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt index 86a5cc4ac..c77c65bbe 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.exceptions /** Thrown when attempting to attach a reporting already attached to a mission. */ +@Deprecated("Use `BackendUsageException` instead.") class ReportingAlreadyAttachedException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministration.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministration.kt index 017c4b34d..2cfed8dcf 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministration.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministration.kt @@ -1,7 +1,8 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.administration import fr.gouv.cacem.monitorenv.config.UseCase -import fr.gouv.cacem.monitorenv.domain.exceptions.CouldNotArchiveException +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IAdministrationRepository @UseCase @@ -11,7 +12,8 @@ class ArchiveAdministration( ) { fun execute(administrationId: Int) { if (!canArchiveAdministration.execute(administrationId)) { - throw CouldNotArchiveException( + throw BackendUsageException( + BackendUsageErrorCode.UNARCHIVED_CHILD, "Cannot archive administration (ID=$administrationId) due to some of its control units not being archived.", ) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContact.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContact.kt index 17180e2c0..fc5d54ef1 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContact.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContact.kt @@ -3,10 +3,55 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit import fr.gouv.cacem.monitorenv.config.UseCase import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitContactRepository +import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitRepository @UseCase -class CreateOrUpdateControlUnitContact(private val controlUnitContactRepository: IControlUnitContactRepository) { +class CreateOrUpdateControlUnitContact( + private val controlUnitRepository: IControlUnitRepository, + private val controlUnitContactRepository: IControlUnitContactRepository, +) { fun execute(controlUnitContact: ControlUnitContactEntity): ControlUnitContactEntity { - return controlUnitContactRepository.save(controlUnitContact) + val validControlUnitContact = validateSubscriptions(controlUnitContact) + + val createdOrUpdatedControlUnitContact = controlUnitContactRepository.save(validControlUnitContact) + + // There can only be one contact per control unit with an email subscription + if (controlUnitContact.id != null && controlUnitContact.isEmailSubscriptionContact) { + unsubscribeOtherContactsFromEmail(controlUnitContact.controlUnitId, controlUnitContact.id) + } + + return createdOrUpdatedControlUnitContact + } + + private fun unsubscribeOtherContactsFromEmail(controlUnitId: Int, controlUnitContactId: Int) { + val fullControlUnit = controlUnitRepository.findById(controlUnitId) + val otherContactsWithEmailSubscription = fullControlUnit.controlUnitContacts + // Filter and not find in the spirit of defensive programming + .filter { it.id != controlUnitContactId && it.isEmailSubscriptionContact } + + otherContactsWithEmailSubscription.forEach { + val updatedControlUnitContact = it.copy(isEmailSubscriptionContact = false) + + controlUnitContactRepository.save(updatedControlUnitContact) + } + } + + /** + * If the contact is subscribed to emails/sms but has no email/phone, we unsubscribe them from emails/sms. + */ + private fun validateSubscriptions(controlUnitContact: ControlUnitContactEntity): ControlUnitContactEntity { + return controlUnitContact.copy( + isEmailSubscriptionContact = if (controlUnitContact.isEmailSubscriptionContact && controlUnitContact.email == null) { + false + } else { + controlUnitContact.isEmailSubscriptionContact + }, + + isSmsSubscriptionContact = if (controlUnitContact.isSmsSubscriptionContact && controlUnitContact.phone == null) { + false + } else { + controlUnitContact.isSmsSubscriptionContact + }, + ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt index f37b2b7fa..956906f4a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt @@ -3,14 +3,14 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions import fr.gouv.cacem.monitorenv.config.UseCase -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository import org.slf4j.LoggerFactory import java.time.ZonedDateTime -import java.util.UUID +import java.util.* @UseCase class DeleteMission( @@ -40,8 +40,8 @@ class DeleteMission( } throw BackendUsageException( - ErrorCode.EXISTING_MISSION_ACTION, - errorSources, + code = BackendUsageErrorCode.EXISTING_MISSION_ACTION, + data = errorSources, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/PatchableDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/PatchableDataInput.kt new file mode 100644 index 000000000..745371ee7 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/PatchableDataInput.kt @@ -0,0 +1,84 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendInternalException +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestErrorCode +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestException +import kotlin.reflect.KClass +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor + +abstract class PatchableDataInput>(private val clazz: KClass) { + fun patchFromRequestData( + objectMapper: ObjectMapper, + requestDataJson: String, + ): T { + val nextDataFromRequestAsJsonNode = objectMapper.readTree(requestDataJson) + + val constructor = clazz.primaryConstructor!! + val params = constructor.parameters.associateWith { parameter -> + val propType = clazz.memberProperties.find { it.name == parameter.name } + val nextPropValueFromRequest = nextDataFromRequestAsJsonNode.get(parameter.name) + + // - If the JSON property is absent, `JsonNode.get()` returns `null`. + // - If the JSON property is explicitly set to `null`, `JsonNode.isNull()` returns `true`. + if (nextPropValueFromRequest != null) { + if (nextPropValueFromRequest.isNull) { + return@associateWith null + } + + return@associateWith convertJsonValueToType( + nextPropValueFromRequest, + parameter.name + ?: throw BackendInternalException( + "${this::class.simpleName}: Property name not found.", + ), + propType?.returnType?.classifier as? KClass<*> + ?: throw BackendInternalException( + "${this::class.simpleName}: Type for `${parameter.name}` not found.", + ), + ) + } + + return@associateWith propType?.getter?.call(this) + } + + return constructor.callBy(params) + } + + private fun convertJsonValueToType(jsonNode: JsonNode, propName: String, propType: KClass<*>): Any { + return when (propType) { + Boolean::class -> if (jsonNode.isBoolean) { + jsonNode.asBoolean() + } else { + throw BackendRequestException( + BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE, + "${this::class.simpleName}: Property `$propName` is not of type `Boolean`.", + ) + } + + Int::class -> if (jsonNode.canConvertToInt()) { + jsonNode.asInt() + } else { + throw BackendRequestException( + BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE, + "${this::class.simpleName}: Property `$propName` is not of type `Int`.", + ) + } + + String::class -> if (jsonNode.isTextual) { + jsonNode.asText() + } else { + throw BackendRequestException( + BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE, + "${this::class.simpleName}: Property `$propName` is not of type `String`.", + ) + } + + else -> throw BackendInternalException( + "${this::class.simpleName}: Unsupported type `$propType` for property `$propName`.", + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt index d3a763ab1..a65d407c4 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt @@ -1,5 +1,8 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs +@Deprecated( + "Use either `BackendInternalErrorDataOutput`, `BackendRequestErrorDataOutput` or `BackendUsageErrorDataOutput` instead.", +) class ApiError(val type: String) { constructor(exception: Throwable) : this(exception.cause?.javaClass?.simpleName.toString()) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendInternalErrorDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendInternalErrorDataOutput.kt new file mode 100644 index 000000000..0cc635d21 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendInternalErrorDataOutput.kt @@ -0,0 +1,14 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs + +/** + * Domain exception to throw when an internal error occurred in the backend. + * + * ## Examples + * - An unexpected exception has been caught. + * + * ## Logging + * The related exception is logged as an error on the Backend side. + */ +class BackendInternalErrorDataOutput { + val message: String = "An internal error occurred." +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendRequestErrorDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendRequestErrorDataOutput.kt new file mode 100644 index 000000000..0943243e2 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendRequestErrorDataOutput.kt @@ -0,0 +1,20 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs + +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestErrorCode + +/** + * Error output to use when the request is invalid. + * + * ## Examples + * - The request has missing or wrongly-typed properties. + * - The request has valid properties but is made of illogical data. + * + * ## Logging + * The related exception is logged as a warning on the Backend side in order to track any unexpected behavior. + * It should definitely be logged on the Frontend side as an error. + */ +data class BackendRequestErrorDataOutput( + val code: BackendRequestErrorCode, + val data: Any? = null, + val message: String? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageError.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageError.kt deleted file mode 100644 index 2e9c639c9..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageError.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs - -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode - -class BackendUsageError(val code: ErrorCode, val data: Any) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageErrorDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageErrorDataOutput.kt new file mode 100644 index 000000000..29698077b --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageErrorDataOutput.kt @@ -0,0 +1,26 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs + +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode + +/** + * Error output to use when the request is valid but the backend cannot process it. + * + * It's called "usage" because this request likely comes from an end-user action that's no longer valid + * which happens when their client data is not up-to-date with the backend. + * + * But it can also be a Frontend side bug. + * + * ## Examples + * - A user tries to create a resource that has already been created. + * - A user tries to delete a resource that doesn't exist anymore. + * + * ## Logging + * The related exception is NOT logged on the Backend side. + * It should be logged on the Frontend side IF it's unexpected (= Frontend bug), + * it should rather display a comprehensible error message to the end-user. + */ +data class BackendUsageErrorDataOutput( + val code: BackendUsageErrorCode, + val data: Any? = null, + val message: String? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateControlUnitContactDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateControlUnitContactDataInputV1.kt similarity index 70% rename from backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateControlUnitContactDataInput.kt rename to backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateControlUnitContactDataInputV1.kt index a950aefec..59b5966c0 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateControlUnitContactDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateControlUnitContactDataInputV1.kt @@ -2,18 +2,20 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity -data class CreateOrUpdateControlUnitContactDataInput( - val id: Int? = null, +data class CreateControlUnitContactDataInputV1( + val id: Int?, val controlUnitId: Int, - val email: String? = null, + val email: String?, val name: String, - val phone: String? = null, + val phone: String?, ) { fun toControlUnitContact(): ControlUnitContactEntity { return ControlUnitContactEntity( id = this.id, controlUnitId = this.controlUnitId, email = this.email, + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = this.name, phone = this.phone, ) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateControlUnitContactDataInputV2.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateControlUnitContactDataInputV2.kt new file mode 100644 index 000000000..e95eba9be --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateControlUnitContactDataInputV2.kt @@ -0,0 +1,42 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs + +import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.PatchableDataInput + +data class CreateOrUpdateControlUnitContactDataInputV2( + val id: Int?, + val controlUnitId: Int, + val email: String?, + val isEmailSubscriptionContact: Boolean, + val isSmsSubscriptionContact: Boolean, + val name: String, + val phone: String?, +) : PatchableDataInput( + CreateOrUpdateControlUnitContactDataInputV2::class, +) { + fun toControlUnitContact(): ControlUnitContactEntity { + return ControlUnitContactEntity( + id = this.id, + controlUnitId = this.controlUnitId, + email = this.email, + isEmailSubscriptionContact = this.isEmailSubscriptionContact, + isSmsSubscriptionContact = this.isSmsSubscriptionContact, + name = this.name, + phone = this.phone, + ) + } + + companion object { + fun fromControlUnitContact(controlUnitContact: ControlUnitContactEntity): CreateOrUpdateControlUnitContactDataInputV2 { + return CreateOrUpdateControlUnitContactDataInputV2( + id = controlUnitContact.id, + controlUnitId = controlUnitContact.controlUnitId, + email = controlUnitContact.email, + isEmailSubscriptionContact = controlUnitContact.isEmailSubscriptionContact, + isSmsSubscriptionContact = controlUnitContact.isSmsSubscriptionContact, + name = controlUnitContact.name, + phone = controlUnitContact.phone, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/ControlUnitContactDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/ControlUnitContactDataOutput.kt index 016992c9a..9935c1942 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/ControlUnitContactDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/ControlUnitContactDataOutput.kt @@ -5,9 +5,11 @@ import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEn data class ControlUnitContactDataOutput( val id: Int, val controlUnitId: Int, - val email: String? = null, + val email: String?, + val isEmailSubscriptionContact: Boolean, + val isSmsSubscriptionContact: Boolean, val name: String, - val phone: String? = null, + val phone: String?, ) { companion object { fun fromControlUnitContact(controlUnitContact: ControlUnitContactEntity): ControlUnitContactDataOutput { @@ -15,6 +17,8 @@ data class ControlUnitContactDataOutput( id = requireNotNull(controlUnitContact.id), controlUnitId = controlUnitContact.controlUnitId, email = controlUnitContact.email, + isEmailSubscriptionContact = controlUnitContact.isEmailSubscriptionContact, + isSmsSubscriptionContact = controlUnitContact.isSmsSubscriptionContact, name = controlUnitContact.name, phone = controlUnitContact.phone, ) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/FullControlUnitContactDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/FullControlUnitContactDataOutput.kt index 14777e0c5..b9c9988dd 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/FullControlUnitContactDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/FullControlUnitContactDataOutput.kt @@ -9,6 +9,8 @@ data class FullControlUnitContactDataOutput( val email: String? = null, val name: String, val phone: String? = null, + val isEmailSubscriptionContact: Boolean? = false, + val isSmsSubscriptionContact: Boolean? = false, ) { companion object { fun fromFullControlUnitContact( @@ -23,6 +25,8 @@ data class FullControlUnitContactDataOutput( email = fullControlUnitContact.controlUnitContact.email, name = fullControlUnitContact.controlUnitContact.name, phone = fullControlUnitContact.controlUnitContact.phone, + isEmailSubscriptionContact = fullControlUnitContact.controlUnitContact.isEmailSubscriptionContact, + isSmsSubscriptionContact = fullControlUnitContact.controlUnitContact.isSmsSubscriptionContact, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt index fff145bae..d3d8cff23 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt @@ -1,12 +1,11 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendInternalException +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.exceptions.ReportingAlreadyAttachedException -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.ApiError -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.BackendUsageError -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.MissingParameterApiError -import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions.UnarchivedChildException +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.* +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestException import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.core.Ordered.HIGHEST_PRECEDENCE @@ -20,53 +19,92 @@ import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice @Order(HIGHEST_PRECEDENCE) -class ControllersExceptionHandler() { +class ControllersExceptionHandler { private val logger: Logger = LoggerFactory.getLogger(ControllersExceptionHandler::class.java) + // ------------------------------------------------------------------------- + // Domain exceptions + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(BackendInternalException::class) + fun handleBackendInternalException(e: BackendInternalException): BackendInternalErrorDataOutput { + return BackendInternalErrorDataOutput() + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(BackendRequestException::class) + fun handleBackendRequestException(e: BackendRequestException): BackendRequestErrorDataOutput { + return BackendRequestErrorDataOutput(code = e.code, data = e.data, message = null) + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(BackendUsageException::class) + fun handleBackendUsageException(e: BackendUsageException): BackendUsageErrorDataOutput { + return BackendUsageErrorDataOutput(code = e.code, data = e.data, message = null) + } + + // ------------------------------------------------------------------------- + // Legacy exceptions + + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(IllegalArgumentException::class) fun handleIllegalArgumentException(e: Exception): ApiError { logger.error(e.message, e) + return ApiError(IllegalArgumentException(e.message.toString(), e)) } + // TODO Migrate to new error handling logic. + // Which cases does this exception cover? @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(NoSuchElementException::class) fun handleNoSuchElementException(e: Exception): ApiError { logger.error(e.message, e) + return ApiError(NoSuchElementException(e.message.toString(), e)) } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MissingServletRequestParameterException::class) fun handleNoParameter(e: MissingServletRequestParameterException): MissingParameterApiError { logger.error(e.message, e) + return MissingParameterApiError("Parameter \"${e.parameterName}\" is missing.") } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(HttpMessageNotReadableException::class) fun handleNoParameter(e: HttpMessageNotReadableException): ApiError { logger.error(e.message, e) - return ApiError(e) - } - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(UnarchivedChildException::class) - fun handleUnarchivedChildException(e: UnarchivedChildException): ApiError { - logger.error(e.message, e) - return ApiError(ErrorCode.UNARCHIVED_CHILD.name) + return ApiError(e) } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ReportingAlreadyAttachedException::class) fun handleReportingAlreadyAttachedToAMission(e: ReportingAlreadyAttachedException): ApiError { - return ApiError(ErrorCode.CHILD_ALREADY_ATTACHED.name) + return ApiError(BackendUsageErrorCode.CHILD_ALREADY_ATTACHED.name) } - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(BackendUsageException::class) - fun handleBackendUsageError(e: BackendUsageException): BackendUsageError { - return BackendUsageError(code = e.code, data = e.data) + // ------------------------------------------------------------------------- + // Infrastructure and unhandled domain exceptions + // - Unhandled domain exceptions are a bug, thus an unexpected exception. + // - Infrastructure exceptions are not supposed to bubble up until here. + // They should be caught or transformed into domain exceptions. + // If that happens, it's a bug, thus an unexpected exception. + + /** + * Catch-all for unexpected exceptions. + */ + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception::class) + fun handleUnexpectedException(e: Exception): BackendInternalErrorDataOutput { + logger.error(e.message, e) + + return BackendInternalErrorDataOutput() } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContacts.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContacts.kt new file mode 100644 index 000000000..c1026f05c --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContacts.kt @@ -0,0 +1,123 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi + +import com.fasterxml.jackson.databind.ObjectMapper +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.CreateOrUpdateControlUnitContact +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.DeleteControlUnitContact +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContactById +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContacts +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateControlUnitContactDataInputV1 +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateControlUnitContactDataInputV2 +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.outputs.ControlUnitContactDataOutput +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.outputs.FullControlUnitContactDataOutput +import fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.utils.validateId +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.websocket.server.PathParam +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api") +@Tag(name = "Public.Control Unit Contacts") +class ControlUnitContacts( + private val createOrUpdateControlUnitContact: CreateOrUpdateControlUnitContact, + private val deleteControlUnitContact: DeleteControlUnitContact, + private val getControlUnitContacts: GetControlUnitContacts, + private val getControlUnitContactById: GetControlUnitContactById, + private val objectMapper: ObjectMapper, +) { + @PostMapping("/v1/control_unit_contacts", consumes = ["application/json"]) + @Operation(summary = "Create a control unit contact") + @ResponseStatus(HttpStatus.CREATED) + @Deprecated("Use POST /api/v2/control_unit_contacts") + fun createV1( + @RequestBody createControlUnitContactDataInput: CreateControlUnitContactDataInputV1, + ): ControlUnitContactDataOutput { + val newControlUnitContact = createControlUnitContactDataInput.toControlUnitContact() + val createdControlUnitContact = createOrUpdateControlUnitContact.execute(newControlUnitContact) + + return ControlUnitContactDataOutput.fromControlUnitContact(createdControlUnitContact) + } + + @PostMapping("/v2/control_unit_contacts", consumes = ["application/json"]) + @Operation(summary = "Create a control unit contact") + @ResponseStatus(HttpStatus.CREATED) + fun createV2( + @RequestBody createControlUnitContactDataInput: CreateOrUpdateControlUnitContactDataInputV2, + ): ControlUnitContactDataOutput { + val newControlUnitContact = createControlUnitContactDataInput.toControlUnitContact() + val createdControlUnitContact = createOrUpdateControlUnitContact.execute(newControlUnitContact) + + return ControlUnitContactDataOutput.fromControlUnitContact(createdControlUnitContact) + } + + @DeleteMapping("/v1/control_unit_contacts/{controlUnitContactId}") + @Operation(summary = "Delete a control unit contact") + @ResponseStatus(HttpStatus.NO_CONTENT) + fun deleteV1( + @PathParam("Control unit contact ID") + @PathVariable(name = "controlUnitContactId") + controlUnitContactId: Int, + ) { + deleteControlUnitContact.execute(controlUnitContactId) + } + + @GetMapping("/v1/control_unit_contacts/{controlUnitContactId}") + @Operation(summary = "Get a control unit contact by its ID") + fun getV1( + @PathParam("Control unit contact ID") + @PathVariable(name = "controlUnitContactId") + controlUnitContactId: Int, + ): FullControlUnitContactDataOutput { + val foundFullControlUnitContact = getControlUnitContactById.execute(controlUnitContactId) + + return FullControlUnitContactDataOutput.fromFullControlUnitContact(foundFullControlUnitContact) + } + + @GetMapping("/v1/control_unit_contacts") + @Operation(summary = "List control unit contacts") + fun getAllV1(): List { + val foundFullControlUnitContacts = getControlUnitContacts.execute() + + return foundFullControlUnitContacts.map { FullControlUnitContactDataOutput.fromFullControlUnitContact(it) } + } + + @PatchMapping(value = ["/v1/control_unit_contacts/{controlUnitContactId}"], consumes = ["application/json"]) + @Operation(summary = "Patch a control unit contact") + fun patchV1( + @PathParam("Control unit contact ID") + @PathVariable(name = "controlUnitContactId") + controlUnitContactId: Int, + @RequestBody partialControlUnitContactAsJson: String, + ): ControlUnitContactDataOutput { + val existingFullControlUnitContact = getControlUnitContactById.execute(controlUnitContactId) + val patchedControlUnitContact = CreateOrUpdateControlUnitContactDataInputV2 + .fromControlUnitContact(existingFullControlUnitContact.controlUnitContact) + .patchFromRequestData(objectMapper, partialControlUnitContactAsJson) + .toControlUnitContact() + val updatedControlUnitContact = createOrUpdateControlUnitContact.execute(patchedControlUnitContact) + + return ControlUnitContactDataOutput.fromControlUnitContact(updatedControlUnitContact) + } + + @PutMapping(value = ["/v1/control_unit_contacts/{controlUnitContactId}"], consumes = ["application/json"]) + @Operation(summary = "Update a control unit contact") + @Deprecated("Use PATCH /api/v2/control_unit_contacts/{controlUnitContactId}") + fun updateV1( + @PathParam("Control unit contact ID") + @PathVariable(name = "controlUnitContactId") + controlUnitContactId: Int, + @RequestBody incompleteControlUnitContactAsJson: String, + ): ControlUnitContactDataOutput { + validateId(incompleteControlUnitContactAsJson, "id", controlUnitContactId, objectMapper) + + val existingFullControlUnitContact = getControlUnitContactById.execute(controlUnitContactId) + val patchedControlUnitContact = CreateOrUpdateControlUnitContactDataInputV2 + .fromControlUnitContact(existingFullControlUnitContact.controlUnitContact) + .patchFromRequestData(objectMapper, incompleteControlUnitContactAsJson) + .toControlUnitContact() + val updatedControlUnitContact = createOrUpdateControlUnitContact.execute(patchedControlUnitContact) + + return ControlUnitContactDataOutput.fromControlUnitContact(updatedControlUnitContact) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v1/ControlUnitContacts.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v1/ControlUnitContacts.kt deleted file mode 100644 index 678a8fe03..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v1/ControlUnitContacts.kt +++ /dev/null @@ -1,85 +0,0 @@ -package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi.v1 - -import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.CreateOrUpdateControlUnitContact -import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.DeleteControlUnitContact -import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContactById -import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContacts -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateControlUnitContactDataInput -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.outputs.ControlUnitContactDataOutput -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.outputs.FullControlUnitContactDataOutput -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.websocket.server.PathParam -import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.* - -@RestController -@RequestMapping("/api/v1/control_unit_contacts") -@Tag(name = "Public.Control Unit Contacts") -class ControlUnitContacts( - private val createOrUpdateControlUnitContact: CreateOrUpdateControlUnitContact, - private val deleteControlUnitContact: DeleteControlUnitContact, - private val getControlUnitContacts: GetControlUnitContacts, - private val getControlUnitContactById: GetControlUnitContactById, -) { - @PostMapping("", consumes = ["application/json"]) - @Operation(summary = "Create a control unit contact") - @ResponseStatus(HttpStatus.CREATED) - fun create( - @RequestBody createControlUnitContactDataInput: CreateOrUpdateControlUnitContactDataInput, - ): ControlUnitContactDataOutput { - val newControlUnitContact = createControlUnitContactDataInput.toControlUnitContact() - val createdControlUnitContact = createOrUpdateControlUnitContact.execute(newControlUnitContact) - - return ControlUnitContactDataOutput.fromControlUnitContact(createdControlUnitContact) - } - - @DeleteMapping("/{controlUnitContactId}") - @Operation(summary = "Delete a control unit contact") - fun delete( - @PathParam("Control unit contact ID") - @PathVariable(name = "controlUnitContactId") - controlUnitContactId: Int, - ) { - deleteControlUnitContact.execute(controlUnitContactId) - } - - @GetMapping("/{controlUnitContactId}") - @Operation(summary = "Get a control unit contact by its ID") - fun get( - @PathParam("Control unit contact ID") - @PathVariable(name = "controlUnitContactId") - controlUnitContactId: Int, - ): FullControlUnitContactDataOutput { - val foundFullControlUnitContact = getControlUnitContactById.execute(controlUnitContactId) - - return FullControlUnitContactDataOutput.fromFullControlUnitContact(foundFullControlUnitContact) - } - - @GetMapping("") - @Operation(summary = "List control unit contacts") - fun getAll(): List { - val foundFullControlUnitContacts = getControlUnitContacts.execute() - - return foundFullControlUnitContacts.map { FullControlUnitContactDataOutput.fromFullControlUnitContact(it) } - } - - @PutMapping(value = ["/{controlUnitContactId}"], consumes = ["application/json"]) - @Operation(summary = "Update a control unit contact") - fun update( - @PathParam("Control unit contact ID") - @PathVariable(name = "controlUnitContactId") - controlUnitContactId: Int, - @RequestBody updateControlUnitContactDataInput: CreateOrUpdateControlUnitContactDataInput, - ): ControlUnitContactDataOutput { - requireNotNull(updateControlUnitContactDataInput.id) { "`id` can't be null." } - require(controlUnitContactId == updateControlUnitContactDataInput.id) { - "Body ID ('${updateControlUnitContactDataInput.id}') doesn't match path ID ('$controlUnitContactId')." - } - - val nextControlUnitContact = updateControlUnitContactDataInput.toControlUnitContact() - val updatedControlUnitContact = createOrUpdateControlUnitContact.execute(nextControlUnitContact) - - return ControlUnitContactDataOutput.fromControlUnitContact(updatedControlUnitContact) - } -} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/utils/validateId.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/utils/validateId.kt new file mode 100644 index 000000000..7e2df172b --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/utils/validateId.kt @@ -0,0 +1,39 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.utils + +import com.fasterxml.jackson.databind.ObjectMapper +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestErrorCode +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestException + +fun validateId(requestDataAsJson: String, idPropName: String, idFromRequestPath: Int, objectMapper: ObjectMapper) { + val requestDataAsJsonNode = objectMapper.readTree(requestDataAsJson) + val idAsJsonNode = requestDataAsJsonNode.get(idPropName) + + if (idAsJsonNode == null) { + throw BackendRequestException( + BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE, + "`$idPropName` is missing in the request data.", + ) + } + + if (idAsJsonNode.isNull) { + throw BackendRequestException( + BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE, + "`$idPropName` is `null` in the request data.", + ) + } + + if (!idAsJsonNode.isInt) { + throw BackendRequestException( + BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE, + "`$idPropName` must be an integer in the request data.", + ) + } + + val requestDataId = idAsJsonNode.asInt() + if (requestDataId != idFromRequestPath) { + throw BackendRequestException( + BackendRequestErrorCode.BODY_ID_MISMATCH_REQUEST_PATH_ID, + "Request data `$idPropName` ('$requestDataId') doesn't match the {$idPropName} in the request path ('$idFromRequestPath').", + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ControlUnitContactModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ControlUnitContactModel.kt index 31d0682dc..1735e8912 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ControlUnitContactModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ControlUnitContactModel.kt @@ -22,13 +22,19 @@ data class ControlUnitContactModel( val controlUnit: ControlUnitModel, @Column(name = "email") - val email: String? = null, + val email: String?, + + @Column(name = "is_email_subscription_contact") + val isEmailSubscriptionContact: Boolean, + + @Column(name = "is_sms_subscription_contact") + val isSmsSubscriptionContact: Boolean, @Column(name = "name") val name: String, @Column(name = "phone") - val phone: String? = null, + val phone: String?, @Column(name = "created_at_utc", nullable = false, updatable = false) @CreationTimestamp @@ -47,6 +53,8 @@ data class ControlUnitContactModel( id = controlUnitContact.id, controlUnit = controlUnitModel, email = controlUnitContact.email, + isEmailSubscriptionContact = controlUnitContact.isEmailSubscriptionContact, + isSmsSubscriptionContact = controlUnitContact.isSmsSubscriptionContact, name = controlUnitContact.name, phone = controlUnitContact.phone, ) @@ -58,6 +66,8 @@ data class ControlUnitContactModel( id, controlUnitId = requireNotNull(controlUnit.id), email, + isEmailSubscriptionContact, + isSmsSubscriptionContact, name, phone, ) @@ -72,6 +82,6 @@ data class ControlUnitContactModel( @Override override fun toString(): String { - return this::class.simpleName + "(id = $id , controlUnitId = ${controlUnit.id} , email = $email , name = $name , phone = $phone)" + return this::class.simpleName + "(id = $id , controlUnitId = ${controlUnit.id} , email = $email , name = $name , phone = $phone, isEmailSubscriptionContact = $isEmailSubscriptionContact, isSmsSubscriptionContact = $isSmsSubscriptionContact)" } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepository.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepository.kt index c016ddee7..fec471c4c 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepository.kt @@ -5,7 +5,6 @@ import fr.gouv.cacem.monitorenv.domain.exceptions.NotFoundException import fr.gouv.cacem.monitorenv.domain.repositories.IAdministrationRepository import fr.gouv.cacem.monitorenv.domain.use_cases.administration.dtos.FullAdministrationDTO import fr.gouv.cacem.monitorenv.infrastructure.database.model.AdministrationModel -import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions.UnarchivedChildException import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBAdministrationRepository import org.springframework.dao.InvalidDataAccessApiUsageException import org.springframework.stereotype.Repository @@ -17,13 +16,6 @@ class JpaAdministrationRepository( ) : IAdministrationRepository { @Transactional override fun archiveById(administrationId: Int) { - val fullAdministration = findById(administrationId) - if (fullAdministration.controlUnits.any { !it.isArchived }) { - throw UnarchivedChildException( - "Cannot archive administration (ID=$administrationId) due to some of its control units not being archived.", - ) - } - dbAdministrationRepository.archiveById(administrationId) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt deleted file mode 100644 index 54d84e739..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions - -/** Thrown when attempting to archive an entity linked to non-archived child(ren). */ -class UnarchivedChildException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/exceptions/BackendRequestErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/exceptions/BackendRequestErrorCode.kt new file mode 100644 index 000000000..e7ab386d7 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/exceptions/BackendRequestErrorCode.kt @@ -0,0 +1,23 @@ +package fr.gouv.cacem.monitorenv.infrastructure.exceptions + +/** + * Error code thrown when the request is invalid. + * + * ## Examples + * - The request has missing or wrongly-typed properties. + * - The request has valid properties but is made of illogical data. + * + * ## Logging + * The related exception is logged as a warning on the Backend side in order to track any unexpected behavior. + * It should definitely be logged on the Frontend side as an error. + * + * ## ⚠️ Important + * **Don't forget to mirror any update here in the corresponding Frontend enum.** + */ +enum class BackendRequestErrorCode { + /** Thrown when the request body ID doesn't match the ID in the request path. */ + BODY_ID_MISMATCH_REQUEST_PATH_ID, + + /** Thrown when a request body property has an unexpected type. */ + WRONG_REQUEST_BODY_PROPERTY_TYPE, +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/exceptions/BackendRequestException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/exceptions/BackendRequestException.kt new file mode 100644 index 000000000..4a7827142 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/exceptions/BackendRequestException.kt @@ -0,0 +1,30 @@ +package fr.gouv.cacem.monitorenv.infrastructure.exceptions + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +/** + * Domain exception to throw when a request is invalid. + * + * ## Examples + * - The request has missing or wrongly-typed properties. + * - The request has valid properties but is made of illogical data. + * + * ## Logging + * This exception is logged as a warning on the Backend side in order to track any unexpected behavior. + * It should definitely be logged on the Frontend side as an error. + * + * ## ⚠️ Important + * **Don't forget to mirror any update here in the corresponding Frontend enum.** + */ +open class BackendRequestException( + val code: BackendRequestErrorCode, + final override val message: String? = null, + val data: Any? = null, +) : Throwable(code.name) { + private val logger: Logger = LoggerFactory.getLogger(BackendRequestException::class.java) + + init { + logger.warn("$code: ${message ?: "No message."}") + } +} diff --git a/backend/src/main/resources/db/migration/internal/V0.127__create_emails_sent_to_control_units_table.sql b/backend/src/main/resources/db/migration/internal/V0.127__create_emails_sent_to_control_units_table.sql new file mode 100644 index 000000000..bc7e3abad --- /dev/null +++ b/backend/src/main/resources/db/migration/internal/V0.127__create_emails_sent_to_control_units_table.sql @@ -0,0 +1,13 @@ +CREATE TABLE public.emails_sent_to_control_units ( + id SERIAL PRIMARY KEY, + control_unit_id INTEGER NOT NULL, + control_unit_name VARCHAR NOT NULL, + email_address VARCHAR NOT NULL, + sending_datetime_utc TIMESTAMP NOT NULL, + actions_min_datetime_utc TIMESTAMP NOT NULL, + actions_max_datetime_utc TIMESTAMP NOT NULL, + number_of_actions INTEGER NOT NULL, + success BOOLEAN NOT NULL, + error_code INTEGER, + error_message VARCHAR +); \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/internal/V0.128__update_control_unit_contacts_table.sql b/backend/src/main/resources/db/migration/internal/V0.128__update_control_unit_contacts_table.sql new file mode 100644 index 000000000..0c633fa5c --- /dev/null +++ b/backend/src/main/resources/db/migration/internal/V0.128__update_control_unit_contacts_table.sql @@ -0,0 +1,3 @@ +ALTER TABLE public.control_unit_contacts +ADD COLUMN is_email_subscription_contact BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN is_sms_subscription_contact BOOLEAN NOT NULL DEFAULT false; diff --git a/backend/src/main/resources/db/testdata/V666.07__insert_dummy_control_units.sql b/backend/src/main/resources/db/testdata/V666.07__insert_dummy_control_units.sql index c59f8e5f3..c02b26a85 100644 --- a/backend/src/main/resources/db/testdata/V666.07__insert_dummy_control_units.sql +++ b/backend/src/main/resources/db/testdata/V666.07__insert_dummy_control_units.sql @@ -45,11 +45,11 @@ INSERT INTO public.control_units( SELECT setval('control_units_id_seq', (SELECT max(id) FROM control_units), true); INSERT INTO public.control_unit_contacts - ( id, control_unit_id, name) + ( id, control_unit_id, name, email, phone, is_email_subscription_contact,is_sms_subscription_contact) VALUES - ( 1, 10000, 'Contact 1'), - ( 2, 10000, 'Contact 2'), - ( 3, 10003, 'Contact 3'); + ( 1, 10000, 'Contact 1', 'email_1', '06 01 xx xx xx', true, false), + ( 2, 10000, 'Contact 2', null, '06 02 xx xx xx', false, true), + ( 3, 10003, 'Contact 3', 'email_3', null, false, true); SELECT setval('control_unit_contacts_id_seq', 3, true); diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministrationUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministrationUTests.kt index 40bee2756..896c63f6f 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministrationUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/administration/ArchiveAdministrationUTests.kt @@ -2,11 +2,12 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.administration import com.nhaarman.mockitokotlin2.given import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import fr.gouv.cacem.monitorenv.domain.exceptions.CouldNotArchiveException +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IAdministrationRepository -import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.junit.jupiter.SpringExtension @@ -36,11 +37,10 @@ class ArchiveAdministrationUTests { given(canArchiveAdministration.execute(administrationId)).willReturn(false) - assertThatThrownBy { + val exception = assertThrows { ArchiveAdministration(administrationRepository, canArchiveAdministration).execute(administrationId) } - .isInstanceOf(CouldNotArchiveException::class.java) - verifyNoMoreInteractions(administrationRepository) + assertThat(exception.code).isEqualTo(BackendUsageErrorCode.UNARCHIVED_CHILD) } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContactUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContactUTests.kt index 7ea3f6fbf..81324f63f 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContactUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/CreateOrUpdateControlUnitContactUTests.kt @@ -1,37 +1,212 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit import com.nhaarman.mockitokotlin2.given -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify +import fr.gouv.cacem.monitorenv.domain.entities.administration.AdministrationEntity import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity +import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitEntity import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitContactRepository +import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitRepository +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.dtos.FullControlUnitDTO import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.BDDMockito import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) class CreateOrUpdateControlUnitContactUTests { + @MockBean + private lateinit var controlUnitRepository: IControlUnitRepository + @MockBean private lateinit var controlUnitContactRepository: IControlUnitContactRepository @Test - fun `execute should return save() result`() { + fun `execute should return the expected result`() { + // Given val newControlUnitContact = ControlUnitContactEntity( + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = true, + name = "Contact Name", + phone = "0033123456789", + ) + + val repositoryOutputMock = newControlUnitContact.copy(id = 1) + given(controlUnitContactRepository.save(newControlUnitContact)).willReturn(repositoryOutputMock) + + // When + val result = CreateOrUpdateControlUnitContact(controlUnitRepository, controlUnitContactRepository) + .execute(newControlUnitContact) + + // Then + assertThat(result).isEqualTo(repositoryOutputMock) + + BDDMockito.verifyNoInteractions(controlUnitRepository) + BDDMockito.verify(controlUnitContactRepository).save(newControlUnitContact) + } + + @Test + fun `execute should unsubscribe the contact from emails or sms if they are subscribed to emails or sms but have no email or phone`() { + // Given + val updatedControlUnitContact = ControlUnitContactEntity( + id = 1, controlUnitId = 2, email = null, - name = "Control Unit Contact Name", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = true, + name = "Contact Name", phone = null, ) - val expectedControlUnitContact = newControlUnitContact.copy(id = 0) + val firstRepositoryOutputMock = FullControlUnitDTO( + administration = AdministrationEntity( + id = 3, + isArchived = false, + name = "Administration Name", + ), + controlUnit = ControlUnitEntity( + id = 2, + administrationId = 3, + areaNote = null, + departmentAreaInseeCode = null, + isArchived = false, + name = "Unit Name", + termsNote = null, + ), + controlUnitContacts = listOf( + updatedControlUnitContact.copy( + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + phone = "0033123456789", + ), + ), + controlUnitResources = listOf(), + ) + given(controlUnitRepository.findById(updatedControlUnitContact.controlUnitId)) + .willReturn(firstRepositoryOutputMock) + + val secondRepositoryInputExpectation = updatedControlUnitContact.copy( + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + ) + val secondRepositoryOutputMock = secondRepositoryInputExpectation + given(controlUnitContactRepository.save(secondRepositoryInputExpectation)).willReturn( + secondRepositoryOutputMock, + ) + + // When + val result = CreateOrUpdateControlUnitContact(controlUnitRepository, controlUnitContactRepository) + .execute(updatedControlUnitContact) + + // Then + assertThat(result).isEqualTo(secondRepositoryOutputMock) + + BDDMockito.verify(controlUnitContactRepository).save(secondRepositoryInputExpectation) + } + + @Test + fun `execute should unsubscribe other control unit contacts from emails if a new one is subscribing to emails`() { + // Given + val updatedControlUnitContact = ControlUnitContactEntity( + id = 1, + controlUnitId = 5, + email = "bob@example.org", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, + name = "Contact Name", + phone = "0033123456789", + ) + + val firstRepositoryOutputMock = FullControlUnitDTO( + administration = AdministrationEntity( + id = 6, + isArchived = false, + name = "Administration Name", + ), + controlUnit = ControlUnitEntity( + id = 5, + administrationId = 6, + areaNote = null, + departmentAreaInseeCode = null, + isArchived = false, + name = "Unit Name", + termsNote = null, + ), + controlUnitContacts = listOf( + ControlUnitContactEntity( + id = updatedControlUnitContact.id, + controlUnitId = 5, + email = "contact1@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Contact 1", + phone = null, + ), + ControlUnitContactEntity( + id = 2, + controlUnitId = 5, + email = "contact2@example.org", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, + name = "Contact 2", + phone = null, + ), + ControlUnitContactEntity( + id = 3, + controlUnitId = 5, + email = "contact3@example.org", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, + name = "Contact 3", + phone = null, + ), + ControlUnitContactEntity( + id = 4, + controlUnitId = 5, + email = "contact3@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Contact 3", + phone = null, + ), + ), + controlUnitResources = listOf(), + ) + given(controlUnitRepository.findById(updatedControlUnitContact.controlUnitId)) + .willReturn(firstRepositoryOutputMock) + + val secondRepositoryExpectedInput = firstRepositoryOutputMock.controlUnitContacts[1].copy( + isEmailSubscriptionContact = false, + ) + val secondRepositoryOutputMock = secondRepositoryExpectedInput + given(controlUnitContactRepository.save(secondRepositoryExpectedInput)) + .willReturn(secondRepositoryOutputMock) + + val thirdRepositoryExpectedInput = firstRepositoryOutputMock.controlUnitContacts[2].copy( + isEmailSubscriptionContact = false, + ) + val thirdRepositoryOutputMock = thirdRepositoryExpectedInput + given(controlUnitContactRepository.save(thirdRepositoryExpectedInput)) + .willReturn(thirdRepositoryOutputMock) + + val fourthRepositoryOutputMock = updatedControlUnitContact + given(controlUnitContactRepository.save(updatedControlUnitContact)) + .willReturn(fourthRepositoryOutputMock) - given(controlUnitContactRepository.save(newControlUnitContact)).willReturn(expectedControlUnitContact) + // When + val result = CreateOrUpdateControlUnitContact(controlUnitRepository, controlUnitContactRepository) + .execute(updatedControlUnitContact) - val result = CreateOrUpdateControlUnitContact(controlUnitContactRepository).execute(newControlUnitContact) + // Then + assertThat(result).isEqualTo(fourthRepositoryOutputMock) - verify(controlUnitContactRepository, times(1)).save(newControlUnitContact) - assertThat(result).isEqualTo(expectedControlUnitContact) + BDDMockito.verify(controlUnitRepository).findById(updatedControlUnitContact.controlUnitId) + BDDMockito.verify(controlUnitContactRepository).save(secondRepositoryExpectedInput) + BDDMockito.verify(controlUnitContactRepository).save(thirdRepositoryExpectedInput) + BDDMockito.verify(controlUnitContactRepository).save(updatedControlUnitContact) } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactByIdUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactByIdUTests.kt index 2ee0d2156..def1cf6b4 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactByIdUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactByIdUTests.kt @@ -34,6 +34,8 @@ class GetControlUnitContactByIdUTests { controlUnitId = 0, email = null, name = "Control Unit Contact Name", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, phone = null, ), ) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactsUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactsUTests.kt index a380a3862..c0bbb841f 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/GetControlUnitContactsUTests.kt @@ -13,7 +13,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) class GetControlUnitContactsUTests { - @MockBean private lateinit var controlUnitContactRepository: IControlUnitContactRepository @@ -34,6 +33,8 @@ class GetControlUnitContactsUTests { id = 1, controlUnitId = 1, email = "contact1@example.com", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = "Contact 1", phone = "123-456-7890", ), @@ -52,6 +53,8 @@ class GetControlUnitContactsUTests { id = 2, controlUnitId = 2, email = "contact2@example.com", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = "Contact 2", phone = "098-765-4321", ), diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt index fc23b352d..5e9ba58dd 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt @@ -1,13 +1,15 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions -import com.nhaarman.mockitokotlin2.* -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.given +import com.nhaarman.mockitokotlin2.verify import fr.gouv.cacem.monitorenv.domain.entities.mission.CanDeleteMissionResponse import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository @@ -143,7 +145,6 @@ class DeleteMissionUTests { mission = missionToDelete, attachedReportingIds = null, ), - ) val throwable = Assertions.catchThrowable { @@ -159,8 +160,8 @@ class DeleteMissionUTests { } Assertions.assertThat(throwable).isInstanceOf( BackendUsageException( - ErrorCode.EXISTING_MISSION_ACTION, - errorSources, + code = BackendUsageErrorCode.EXISTING_MISSION_ACTION, + data = errorSources, )::class.java, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/PatchableDataInputUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/PatchableDataInputUTests.kt new file mode 100644 index 000000000..d3211cce0 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/PatchableDataInputUTests.kt @@ -0,0 +1,208 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters + +import com.fasterxml.jackson.databind.ObjectMapper +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendInternalException +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestErrorCode +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.test.context.junit.jupiter.SpringExtension + +data class FakeEntity( + val id: Int, + val name: String, + val description: String?, + val isAwesome: Boolean, + val isDeleted: Boolean, + val isUpdated: Boolean, + val rank: Int, +) + +data class FakeDataInput( + val id: Int, + val name: String, + val description: String?, + val isAwesome: Boolean, + val isDeleted: Boolean, + val isUpdated: Boolean, + val rank: Int, +) : PatchableDataInput(FakeDataInput::class) { + companion object { + fun fromFakeEntity(entity: FakeEntity): FakeDataInput { + return FakeDataInput( + id = entity.id, + name = entity.name, + description = entity.description, + isAwesome = entity.isAwesome, + isDeleted = entity.isDeleted, + isUpdated = entity.isUpdated, + rank = entity.rank, + ) + } + } +} + +@ExtendWith(SpringExtension::class) +class PatchableDataInputUTests { + private lateinit var existingFakeEntityFromDatabase: FakeEntity + private val objectMapper = ObjectMapper() + + @BeforeEach + fun setUp() { + existingFakeEntityFromDatabase = FakeEntity( + id = 1, + name = "A name", + description = "A description", + isAwesome = true, + isDeleted = false, + isUpdated = false, + rank = 2, + ) + } + + @Test + fun `patchFromRequestData Should return the expected data input instance`() { + // Given + val fakeRequestDataAsJson = """ + { + "name": "A new name", + "description": null, + "isUpdated": true + } + """.trimIndent() + + // When + val result = FakeDataInput + .fromFakeEntity(existingFakeEntityFromDatabase) + .patchFromRequestData(objectMapper, fakeRequestDataAsJson) + + // Then + assertThat(result).isEqualTo( + FakeDataInput( + id = 1, + name = "A new name", + description = null, + isAwesome = true, + isDeleted = false, + isUpdated = true, + rank = 2, + ), + ) + } + + @Test + fun `patchFromRequestData Should ignore extra properties from the request data JSON`() { + // Given + val fakeRequestDataAsJson = """ + { + "name": "A new name", + "anNonExistingProperty": "A lost value" + } + """.trimIndent() + + // When + val result = FakeDataInput + .fromFakeEntity(existingFakeEntityFromDatabase) + .patchFromRequestData(objectMapper, fakeRequestDataAsJson) + + // Then + assertThat(result).isEqualTo( + FakeDataInput( + id = 1, + name = "A new name", + description = "A description", + isAwesome = true, + isDeleted = false, + isUpdated = false, + rank = 2, + ), + ) + } + + @Test + fun `patchFromRequestData Should throw a BackendRequestException for an incorrect Boolean type`() { + // Given + val fakeRequestDataAsJson = """ + { + "isAwesome": "Not a Boolean" + } + """.trimIndent() + + // Then + val exception = assertThrows { + // When + FakeDataInput + .fromFakeEntity(existingFakeEntityFromDatabase) + .patchFromRequestData(objectMapper, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE) + assertThat(exception).hasMessageContaining("FakeDataInput: Property `isAwesome` is not of type `Boolean`.") + } + + @Test + fun `patchFromRequestData Should throw a BackendRequestException for an incorrect Int type`() { + // Given + val fakeRequestDataAsJson = """ + { + "rank": "Not an Int" + } + """.trimIndent() + + // Then + val exception = assertThrows { + // When + FakeDataInput + .fromFakeEntity(existingFakeEntityFromDatabase) + .patchFromRequestData(objectMapper, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE) + assertThat(exception).hasMessageContaining("FakeDataInput: Property `rank` is not of type `Int`.") + } + + @Test + fun `patchFromRequestData Should throw a BackendRequestException for an incorrect String type`() { + // Given + val fakeRequestDataAsJson = """ + { + "name": 42 + } + """.trimIndent() + + // Then + val exception = assertThrows { + // When + FakeDataInput + .fromFakeEntity(existingFakeEntityFromDatabase) + .patchFromRequestData(objectMapper, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE) + assertThat(exception).hasMessageContaining("FakeDataInput: Property `name` is not of type `String`.") + } + + @Test + fun `patchFromRequestData Should throw a BackendInternalException for an unsupported type`() { + data class FakeDataInput( + val anUnsupportedTypedProp: Any, + ) : PatchableDataInput(FakeDataInput::class) + + // Given + val fakeRequestDataAsJson = """ + { + "anUnsupportedTypedProp": "Another value" + } + """.trimIndent() + + // Then + val exception = assertThrows { + // When + FakeDataInput(anUnsupportedTypedProp = "A value") + .patchFromRequestData(objectMapper, fakeRequestDataAsJson) + } + assertThat(exception).hasMessageContaining( + "FakeDataInput: Unsupported type `class kotlin.Any` for property `anUnsupportedTypedProp`.", + ) + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContactsITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContactsITests.kt index 083e89e0c..1a2b80bc3 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContactsITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ControlUnitContactsITests.kt @@ -1,7 +1,6 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi import com.fasterxml.jackson.databind.ObjectMapper -import com.nhaarman.mockitokotlin2.any import fr.gouv.cacem.monitorenv.config.MapperConfiguration import fr.gouv.cacem.monitorenv.config.WebSecurityConfig import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity @@ -11,8 +10,8 @@ import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.DeleteControlUnitCo import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContactById import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContacts import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.dtos.FullControlUnitContactDTO -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateControlUnitContactDataInput -import fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi.v1.ControlUnitContacts +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateControlUnitContactDataInputV1 +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateControlUnitContactDataInputV2 import org.hamcrest.Matchers import org.junit.jupiter.api.Test import org.mockito.BDDMockito @@ -26,7 +25,6 @@ import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* import org.springframework.test.web.servlet.result.MockMvcResultHandlers import org.springframework.test.web.servlet.result.MockMvcResultMatchers -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @Import(WebSecurityConfig::class, MapperConfiguration::class) @WebMvcTest(value = [(ControlUnitContacts::class)]) @@ -34,6 +32,9 @@ class ControlUnitContactsITests { @Autowired private lateinit var mockMvc: MockMvc + @Autowired + private lateinit var objectMapper: ObjectMapper + @MockBean private lateinit var createOrUpdateControlUnitContact: CreateOrUpdateControlUnitContact @@ -46,46 +47,111 @@ class ControlUnitContactsITests { @MockBean private lateinit var getControlUnitContacts: GetControlUnitContacts - @Autowired - private lateinit var objectMapper: ObjectMapper - @Test - fun `create() should create a contact`() { - val expectedCreatedControlUnitContact = ControlUnitContactEntity( - id = 1, - controlUnitId = 0, - email = null, + fun `createV1 should create a contact`() { + // Given + val requestDataAsDataInput = CreateControlUnitContactDataInputV1( + id = null, + controlUnitId = 2, + email = "bob@example.org", name = "Contact Name", - phone = null, + phone = "0033123456789", ) + val requestDataAsJson = objectMapper.writeValueAsString(requestDataAsDataInput) - val newControlUnitContactData = CreateOrUpdateControlUnitContactDataInput( - controlUnitId = 0, - email = null, + val useCaseInputExpectation = ControlUnitContactEntity( + id = null, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = "Contact Name", - phone = null, + phone = "0033123456789", ) - val requestBody = objectMapper.writeValueAsString(newControlUnitContactData) - - given(createOrUpdateControlUnitContact.execute(controlUnitContact = any())).willReturn( - expectedCreatedControlUnitContact, + val useCaseOutputMock = useCaseInputExpectation.copy( + id = 1, ) + given(createOrUpdateControlUnitContact.execute(useCaseInputExpectation)) + .willReturn(useCaseOutputMock) + // When mockMvc.perform( post("/api/v1/control_unit_contacts") - .content(requestBody) + .content(requestDataAsJson) .contentType(MediaType.APPLICATION_JSON), ) .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isCreated) + // Then + .andExpect(MockMvcResultMatchers.status().isCreated) + + BDDMockito.verify(createOrUpdateControlUnitContact).execute(useCaseInputExpectation) } @Test - fun `get() should get a contact by its ID`() { - val expectedFullControlUnitContact = FullControlUnitContactDTO( + fun `createV2 should create a contact`() { + // Given + val newControlUnitContactData = CreateOrUpdateControlUnitContactDataInputV2( + id = null, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Contact Name", + phone = "0033123456789", + ) + val requestDataAsJson = objectMapper.writeValueAsString(newControlUnitContactData) + + val useCaseInputExpectation = ControlUnitContactEntity( + id = null, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Contact Name", + phone = "0033123456789", + ) + val useCaseOutputMock = useCaseInputExpectation.copy( + id = 1, + ) + given(createOrUpdateControlUnitContact.execute(useCaseInputExpectation)) + .willReturn(useCaseOutputMock) + + // When + mockMvc.perform( + post("/api/v2/control_unit_contacts") + .content(requestDataAsJson) + .contentType(MediaType.APPLICATION_JSON), + ) + .andDo(MockMvcResultHandlers.print()) + // Then + .andExpect(MockMvcResultMatchers.status().isCreated) + + BDDMockito.verify(createOrUpdateControlUnitContact).execute(useCaseInputExpectation) + } + + @Test + fun `deleteV1() should delete a contact`() { + // Given + val requestedId = 1 + + // When + mockMvc.perform(delete("/api/v1/control_unit_contacts/$requestedId")) + .andDo(MockMvcResultHandlers.print()) + // Then + .andExpect(MockMvcResultMatchers.status().isNoContent) + + BDDMockito.verify(deleteControlUnitContact).execute(requestedId) + } + + @Test + fun `getV1 should get a contact by its ID`() { + // Given + val requestedId = 1 + + val useCaseOutputMock = FullControlUnitContactDTO( controlUnit = ControlUnitEntity( - id = 0, - administrationId = 0, + id = 2, + administrationId = 3, areaNote = null, departmentAreaInseeCode = null, isArchived = false, @@ -94,30 +160,33 @@ class ControlUnitContactsITests { ), controlUnitContact = ControlUnitContactEntity( id = 1, - controlUnitId = 0, - email = null, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = "Contact Name", - phone = null, + phone = "0033123456789", ), ) + given(getControlUnitContactById.execute(requestedId)).willReturn(useCaseOutputMock) - val requestedId = 1 - - given(getControlUnitContactById.execute(requestedId)).willReturn(expectedFullControlUnitContact) - + // When mockMvc.perform(get("/api/v1/control_unit_contacts/$requestedId")) - .andExpect(status().isOk) + .andDo(MockMvcResultHandlers.print()) + // Then + .andExpect(MockMvcResultMatchers.status().isOk) BDDMockito.verify(getControlUnitContactById).execute(requestedId) } @Test - fun `getAll() should get all contacts`() { - val expectedFullControlUnitContacts = listOf( + fun `getAllV1 should get all contacts`() { + // Given + val useCaseOutputMock = listOf( FullControlUnitContactDTO( controlUnit = ControlUnitEntity( - id = 0, - administrationId = 0, + id = 2, + administrationId = 3, areaNote = null, departmentAreaInseeCode = null, isArchived = false, @@ -126,17 +195,19 @@ class ControlUnitContactsITests { ), controlUnitContact = ControlUnitContactEntity( id = 1, - controlUnitId = 0, - email = null, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = "Contact Name", - phone = null, + phone = "0033123456789", ), ), FullControlUnitContactDTO( controlUnit = ControlUnitEntity( - id = 0, - administrationId = 0, + id = 5, + administrationId = 6, areaNote = null, departmentAreaInseeCode = null, isArchived = false, @@ -144,53 +215,146 @@ class ControlUnitContactsITests { termsNote = null, ), controlUnitContact = ControlUnitContactEntity( - id = 2, - controlUnitId = 0, - email = null, + id = 4, + controlUnitId = 5, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, name = "Contact Name 2", - phone = null, + phone = "0033123456789", ), ), ) + given(getControlUnitContacts.execute()).willReturn(useCaseOutputMock) - given(getControlUnitContacts.execute()).willReturn(expectedFullControlUnitContacts) - + // When mockMvc.perform(get("/api/v1/control_unit_contacts")) - .andExpect(status().isOk) - .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2))) + .andDo(MockMvcResultHandlers.print()) + // Then + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.length()", Matchers.equalTo(2))) BDDMockito.verify(getControlUnitContacts).execute() } @Test - fun `update() should update a contact`() { - val expectedUpdatedControlUnitContact = ControlUnitContactEntity( - id = 1, - controlUnitId = 0, - email = null, - name = "Updated Contact Name", - phone = null, + fun `patchV1 should patch a contact`() { + // Given + val requestedId = 1 + val requestDataAsJson = objectMapper.createObjectNode().apply { + put("id", 1) + put("name", "Updated Contact Name") + }.toString() + + val firstUseCaseOutputMock = FullControlUnitContactDTO( + controlUnit = ControlUnitEntity( + id = 2, + administrationId = 3, + areaNote = "Area Note", + departmentAreaInseeCode = "12345", + isArchived = false, + name = "Unit Name", + termsNote = "Terms Note", + ), + controlUnitContact = ControlUnitContactEntity( + id = 1, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Contact Name", + phone = "0033123456789", + ), ) + given(getControlUnitContactById.execute(requestedId)) + .willReturn(firstUseCaseOutputMock) - val nextControlUnitContactData = CreateOrUpdateControlUnitContactDataInput( + val secondUseCaseInputExpectation = ControlUnitContactEntity( id = 1, - email = null, - controlUnitId = 0, - name = "Updated Contact Name", - phone = null, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Updated Contact Name", // Updated property + phone = "0033123456789", ) - val requestBody = objectMapper.writeValueAsString(nextControlUnitContactData) + val secondUseCaseOutputMock = secondUseCaseInputExpectation + given(createOrUpdateControlUnitContact.execute(secondUseCaseInputExpectation)) + .willReturn(secondUseCaseOutputMock) - given(createOrUpdateControlUnitContact.execute(controlUnitContact = any())).willReturn( - expectedUpdatedControlUnitContact, + // When + mockMvc.perform( + patch("/api/v1/control_unit_contacts/$requestedId") + .content(requestDataAsJson) + .contentType(MediaType.APPLICATION_JSON), ) + .andDo(MockMvcResultHandlers.print()) + // Then + .andExpect(MockMvcResultMatchers.status().isOk) + BDDMockito.verify(getControlUnitContactById).execute(requestedId) + BDDMockito.verify(createOrUpdateControlUnitContact).execute(secondUseCaseInputExpectation) + } + + @Test + fun `updateV1 should update a contact`() { + // Given + val requestedId = 1 + val requestDataAsJson = objectMapper.createObjectNode().apply { + put("id", 1) + put("email", "bob@example.org") + put("controlUnitId", 2) + put("name", "Updated Contact Name") + put("phone", "0033123456789") + }.toString() + + val firstUseCaseOutputMock = FullControlUnitContactDTO( + controlUnit = ControlUnitEntity( + id = 2, + administrationId = 3, + areaNote = "Area Note", + departmentAreaInseeCode = "12345", + isArchived = false, + name = "Unit Name", + termsNote = "Terms Note", + ), + controlUnitContact = ControlUnitContactEntity( + id = 1, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Contact Name", + phone = "0033123456789", + ), + ) + given(getControlUnitContactById.execute(requestedId)) + .willReturn(firstUseCaseOutputMock) + + val secondUseCaseInputExpectation = ControlUnitContactEntity( + id = 1, + controlUnitId = 2, + email = "bob@example.org", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = false, + name = "Updated Contact Name", // Updated property + phone = "0033123456789", + ) + val secondUseCaseOutputMock = secondUseCaseInputExpectation + given(createOrUpdateControlUnitContact.execute(secondUseCaseInputExpectation)) + .willReturn(secondUseCaseOutputMock) + + // When mockMvc.perform( - put("/api/v1/control_unit_contacts/1") - .content(requestBody) + put("/api/v1/control_unit_contacts/$requestedId") + .content(requestDataAsJson) .contentType(MediaType.APPLICATION_JSON), ) .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk) + // Then + .andExpect(MockMvcResultMatchers.status().isOk) + + BDDMockito.verify(getControlUnitContactById).execute(requestedId) + BDDMockito.verify(createOrUpdateControlUnitContact).execute(secondUseCaseInputExpectation) } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/utils/ValidateIdUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/utils/ValidateIdUTests.kt new file mode 100644 index 000000000..7383d8177 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/utils/ValidateIdUTests.kt @@ -0,0 +1,111 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.utils + +import com.fasterxml.jackson.databind.ObjectMapper +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestErrorCode +import fr.gouv.cacem.monitorenv.infrastructure.exceptions.BackendRequestException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class FakeController( + private val objectMapper: ObjectMapper, +) { + @PatchMapping(value = ["/api/v1/fakes/{fakeId}"], consumes = ["application/json"]) + fun patch( + @PathVariable(name = "fakeId") + fakeId: Int, + @RequestBody partialFakeAsJson: String, + ) { + validateId(partialFakeAsJson, "id", fakeId, objectMapper) + } +} + +@ExtendWith(SpringExtension::class) +class ValidateIdUTests { + private lateinit var fakeController: FakeController + private val objectMapper = ObjectMapper() + + @BeforeEach + fun setUp() { + fakeController = FakeController(objectMapper) + } + + @Test + fun `validateId should throw an exception when id is missing in JSON`() { + // Given + val fakeRequestDataAsJson = "{}" + + // Then + val exception = assertThrows { + // When + fakeController.patch(1, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE) + assertThat(exception).hasMessageContaining("`id` is missing in the request data.") + } + + @Test + fun `validateId should throw an exception when id is null in JSON`() { + // Given + val fakeRequestDataAsJson = "{ \"id\": null }" + + // Then + val exception = assertThrows { + // When + fakeController.patch(1, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE) + assertThat(exception).hasMessageContaining("`id` is `null` in the request data.") + } + + @Test + fun `validateId should throw an exception when id is not an integer`() { + // Given + val fakeRequestDataAsJson = "{ \"id\": \"not an integer\" }" + + // Then + val exception = assertThrows { + // When + fakeController.patch(1, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.WRONG_REQUEST_BODY_PROPERTY_TYPE) + assertThat(exception).hasMessageContaining("`id` must be an integer in the request data.") + } + + @Test + fun `validateId should throw an exception when id in JSON does not match id from request path`() { + // Given + val fakeRequestDataAsJson = "{ \"id\": 2 }" + + // Then + val exception = assertThrows { + // When + fakeController.patch(1, fakeRequestDataAsJson) + } + assertThat(exception.code).isEqualTo(BackendRequestErrorCode.BODY_ID_MISMATCH_REQUEST_PATH_ID) + assertThat(exception).hasMessageContaining( + "Request data `id` ('2') doesn't match the {id} in the request path ('1').", + ) + } + + @Test + fun `validateId should not throw any exception when id in JSON matches id from request path`() { + // Given + val fakeRequestDataAsJson = "{ \"id\": 1 }" + + // Then + assertDoesNotThrow { + // When + fakeController.patch(1, fakeRequestDataAsJson) + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepositoryITests.kt index 8c630e707..37c03db64 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaAdministrationRepositoryITests.kt @@ -3,9 +3,7 @@ package fr.gouv.cacem.monitorenv.infrastructure.database.repositories import fr.gouv.cacem.monitorenv.domain.entities.administration.AdministrationEntity import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitEntity import fr.gouv.cacem.monitorenv.domain.use_cases.administration.dtos.FullAdministrationDTO -import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions.UnarchivedChildException import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.catchThrowable import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.transaction.annotation.Transactional @@ -28,17 +26,6 @@ class JpaAdministrationRepositoryITests : AbstractDBTests() { assertThat(afterFullAdministration.administration.isArchived).isTrue() } - @Test - @Transactional - fun `archiveById() should throw the expected exception when the administration is linked to unarchived control units`() { - val throwable = catchThrowable { - jpaAdministrationRepository.archiveById(1005) - } - - // Then - assertThat(throwable).isInstanceOf(UnarchivedChildException::class.java) - } - @Test @Transactional fun `findAll() should find all administrations`() { diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitContactRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitContactRepositoryITests.kt index 9a6cfe12d..cff94782b 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitContactRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitContactRepositoryITests.kt @@ -34,8 +34,11 @@ class JpaControlUnitContactRepositoryITests : AbstractDBTests() { controlUnitContact = ControlUnitContactEntity( id = 1, controlUnitId = 10000, + email = "email_1", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, name = "Contact 1", - phone = null, + phone = "06 01 xx xx xx", ), ), ) @@ -54,6 +57,9 @@ class JpaControlUnitContactRepositoryITests : AbstractDBTests() { controlUnitContact = ControlUnitContactEntity( id = 3, controlUnitId = 10003, + email = "email_3", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = true, name = "Contact 3", phone = null, ), @@ -80,8 +86,11 @@ class JpaControlUnitContactRepositoryITests : AbstractDBTests() { controlUnitContact = ControlUnitContactEntity( id = 1, controlUnitId = 10000, + email = "email_1", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, name = "Contact 1", - phone = null, + phone = "06 01 xx xx xx", ), ), ) @@ -95,6 +104,9 @@ class JpaControlUnitContactRepositoryITests : AbstractDBTests() { val newControlUnitContact = ControlUnitContactEntity( controlUnitId = 10000, + email = "Adresse email", + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = true, name = "Contact Name", phone = "0123456789", ) @@ -109,6 +121,9 @@ class JpaControlUnitContactRepositoryITests : AbstractDBTests() { val nextControlUnitContact = ControlUnitContactEntity( id = 4, controlUnitId = 10001, + email = null, + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = true, name = "Updated Contact Name", phone = "9876543210", ) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitRepositoryITests.kt index 610974d72..557a45dcc 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaControlUnitRepositoryITests.kt @@ -14,7 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.transaction.annotation.Transactional class JpaControlUnitRepositoryITests : AbstractDBTests() { - @Autowired private lateinit var jpaControlUnitRepository: JpaControlUnitRepository + @Autowired + private lateinit var jpaControlUnitRepository: JpaControlUnitRepository @Test @Transactional @@ -62,16 +63,20 @@ class JpaControlUnitRepositoryITests : AbstractDBTests() { ControlUnitContactEntity( id = 1, controlUnitId = 10000, - email = null, + email = "email_1", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, name = "Contact 1", - phone = null, + phone = "06 01 xx xx xx", ), ControlUnitContactEntity( id = 2, controlUnitId = 10000, email = null, + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = true, name = "Contact 2", - phone = null, + phone = "06 02 xx xx xx", ), ), controlUnitResources = @@ -303,16 +308,20 @@ class JpaControlUnitRepositoryITests : AbstractDBTests() { ControlUnitContactEntity( id = 1, controlUnitId = 10000, - email = null, + email = "email_1", + isEmailSubscriptionContact = true, + isSmsSubscriptionContact = false, name = "Contact 1", - phone = null, + phone = "06 01 xx xx xx", ), ControlUnitContactEntity( id = 2, controlUnitId = 10000, email = null, + isEmailSubscriptionContact = false, + isSmsSubscriptionContact = true, name = "Contact 2", - phone = null, + phone = "06 02 xx xx xx", ), ), controlUnitResources = diff --git a/datascience/.env.template b/datascience/.env.template index 92c67b795..4bc92effa 100644 --- a/datascience/.env.template +++ b/datascience/.env.template @@ -35,4 +35,17 @@ CACEM_LOCAL_PWD= HTTP_PROXY_= HTTPS_PROXY_= -PREFECT_SERVER_URL= \ No newline at end of file +# Prefect Server endpoint +PREFECT_SERVER_URL= + +# Email settings +EMAIL_SERVER_URL= +EMAIL_SERVER_PORT= +MONITORENV_SENDER_EMAIL_ADDRESS= + +# Recipients +CACEM_EMAIL_ADDRESS= + +# Deployment options +IS_INTEGRATION= +TEST_MODE= \ No newline at end of file diff --git a/datascience/.env.test b/datascience/.env.test index cdca20e56..746915845 100644 --- a/datascience/.env.test +++ b/datascience/.env.test @@ -6,3 +6,9 @@ MONITORENV_REMOTE_DB_USER=postgres MONITORENV_REMOTE_DB_PWD=postgres MONITORENV_URL=https://monitor.fish/ + +# Email settings +MONITORENV_SENDER_EMAIL_ADDRESS="monitorenv@email.fr" + +# Recipients +CACEM_EMAIL_ADDRESS="cacem@email.fr" \ No newline at end of file diff --git a/datascience/config.py b/datascience/config.py index b9cf2b6d1..18aadc9c4 100644 --- a/datascience/config.py +++ b/datascience/config.py @@ -20,6 +20,11 @@ "HOST_MIGRATIONS_FOLDER", LOCAL_MIGRATIONS_FOLDER ) +EMAIL_TEMPLATES_LOCATION = LIBRARY_LOCATION / Path("pipeline/emails/templates") +EMAIL_STYLESHEETS_LOCATION = LIBRARY_LOCATION / Path( + "pipeline/emails/stylesheets" +) + # Must be set to true when running tests locally TEST_LOCAL = os.getenv("TEST_LOCAL", "False").lower() in ( "true", @@ -40,6 +45,24 @@ if RUN_LOCAL: load_dotenv(ROOT_DIRECTORY / ".env") +# Must be set to true to avoid external side effects (emails, data.gouv uploads...) in +# integration +IS_INTEGRATION = os.getenv("IS_INTEGRATION", "False").lower() in ( + "true", + "t", + "yes", + "y", +) + +# Must be set to true to send controls data to the CACEM_EMAIL_ADDRESS, and +# not to real email addressees (control units) +TEST_MODE = os.getenv("TEST_MODE", "False").lower() in ( + "true", + "t", + "yes", + "y", +) + # Flow execution configuration DOCKER_IMAGE = "ghcr.io/mtes-mct/monitorenv/monitorenv-pipeline" MONITORENV_VERSION = os.getenv("MONITORENV_VERSION") @@ -69,3 +92,13 @@ # Shift ids from Poseidon to MonitorEnv POSEIDON_CACEM_MISSION_ID_TO_MONITORENV_MISSION_ID_SHIFT = -100000 + +# Email server +EMAIL_SERVER_URL = os.environ.get("EMAIL_SERVER_URL") +EMAIL_SERVER_PORT = os.environ.get("EMAIL_SERVER_PORT") +MONITORENV_SENDER_EMAIL_ADDRESS = os.environ.get( + "MONITORENV_SENDER_EMAIL_ADDRESS" +) + +# Recipients +CACEM_EMAIL_ADDRESS = os.environ.get("CACEM_EMAIL_ADDRESS") diff --git a/datascience/poetry.lock b/datascience/poetry.lock index 79968a7e7..a4c0860f2 100644 --- a/datascience/poetry.lock +++ b/datascience/poetry.lock @@ -1,10 +1,15 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + [[package]] name = "astroid" version = "2.15.5" description = "An abstract syntax tree for Python with inference support." -category = "dev" optional = false python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.5-py3-none-any.whl", hash = "sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324"}, + {file = "astroid-2.15.5.tar.gz", hash = "sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"}, +] [package.dependencies] lazy-object-proxy = ">=1.4.0" @@ -15,9 +20,12 @@ wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" 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)"] @@ -30,9 +38,35 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] [package.dependencies] click = ">=8.0.0" @@ -52,33 +86,118 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] [[package]] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] [[package]] name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] [[package]] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -87,9 +206,12 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." -category = "main" optional = false python-versions = "*" +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] [package.dependencies] click = ">=4.0" @@ -101,9 +223,12 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "cligj" version = "0.7.2" description = "Click params for commmand line interfaces to GeoJSON" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" +files = [ + {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, + {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, +] [package.dependencies] click = ">=4.0" @@ -115,25 +240,92 @@ test = ["pytest-cov"] name = "cloudpickle" version = "2.2.1" description = "Extended pickling support for Python objects" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"}, + {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, +] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" 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 = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] [package.extras] toml = ["tomli"] @@ -142,28 +334,84 @@ toml = ["tomli"] name = "croniter" version = "1.4.0" description = "croniter provides iteration for datetime object with cron like format" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "croniter-1.4.0-py2.py3-none-any.whl", hash = "sha256:4cb177d0167d4a61b1aa3575fcf06b9a064e9a48a4b11d6ab474b1b9e126ed9c"}, + {file = "croniter-1.4.0.tar.gz", hash = "sha256:edf332f2ef3b2b47d4c01004c640981c5a70d8ccb6b854aea8866a28b532449a"}, +] [package.dependencies] python-dateutil = "*" +[[package]] +name = "css-inline" +version = "0.13.0" +description = "High-performance library for inlining CSS into HTML 'style' attributes" +optional = false +python-versions = ">=3.7" +files = [ + {file = "css_inline-0.13.0-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c06b1cb6e5638328e9f1a62f8a0b760914852834d950b7f6812653231c806ded"}, + {file = "css_inline-0.13.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:af66e7dc7e762ab24ebe57f98b7c02ac87cc10457a64b545de923a47694f63f7"}, + {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cbe8e5b1379695183a1700a70ad22877731b22e223ad4b7ddbd38b6e3b18b484"}, + {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c4861ed5c62412a7f1db87195cb94cfdb7b2ae1f1b4e10a31df7f6217e49bda"}, + {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:30d7d79b939df4ef14587998ad1be350410b3c19e697d0a445f99c7734fdf15a"}, + {file = "css_inline-0.13.0-cp37-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:0111e8c300676aa47b99f30667583ec0a70914a63377ceefa008f5ba75853df3"}, + {file = "css_inline-0.13.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c1a8cb1323579bc38e2b9a55fe2ebb9f95dae5774521d1e870a6548069774197"}, + {file = "css_inline-0.13.0-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c69ca8e8fca9c4d655de091c126d9d69de82b66e3182abdfb37db54c9532caac"}, + {file = "css_inline-0.13.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2f843e58e8907614c39f65898ebddb62e893bf49a022f26ac1fa1a7f53591521"}, + {file = "css_inline-0.13.0-cp37-abi3-win32.whl", hash = "sha256:3963e66e20c554798d58759bb282ef763836b4ee2afa2c6e466b2b31d9475f21"}, + {file = "css_inline-0.13.0-cp37-abi3-win_amd64.whl", hash = "sha256:f3403165e511935de3fd702b667fac2b018e080d96ac04918041788ec409f04b"}, + {file = "css_inline-0.13.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:80db07d589361f1ed75c297bd984049918693016c81cf288cc3dc7276653469a"}, + {file = "css_inline-0.13.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:00410f5bf5bbc50d6f0aa474750aa2c9ebdc51b37af60e15d1ced7e539608492"}, + {file = "css_inline-0.13.0-pp310-pypy310_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3814d5060fa874ea88cc7262f0dec79cd83c63d5528fed049434ff4cd725d296"}, + {file = "css_inline-0.13.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:866dba67fc4065981fc60af72f470e58210eaee59b69ba6ac023f948ee5199b5"}, + {file = "css_inline-0.13.0-pp37-pypy37_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:1fa752b54612e38bfc69083fd77de53df2053bd5f1de2a210e37b3347eb9720d"}, + {file = "css_inline-0.13.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f481d2dfa116c9f1a163c2478b6c4c7d035740a680d19b6cb780677148c595f6"}, + {file = "css_inline-0.13.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:206c1cb0700f00a7164d18ce293bdaa4ca6cdfc4640e04ea5724ef1c4937bd64"}, + {file = "css_inline-0.13.0-pp38-pypy38_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:6761771ea8481fdcb00ce25727e08232561a107a8330f012e52276a6fe84e080"}, + {file = "css_inline-0.13.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d7f8399b05d88c4cf48bcea3a9fe4f6cdab9e4def0e608d9c7e9447dd39f2e72"}, + {file = "css_inline-0.13.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:46296fefce679d760cd824cefcdd4f372dbc23232599a99e94612fc46bc0bfbe"}, + {file = "css_inline-0.13.0-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:4c9e4d17a801240e3d23d047f159a997f01c52d441a2529dddb18557068dbf5b"}, + {file = "css_inline-0.13.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:551619187c6c2d58442e5b67917e4254cec552e38750be942a871bc118ee56d6"}, + {file = "css_inline-0.13.0.tar.gz", hash = "sha256:d1a8366df670f7db78f5da9f8d4f500b3a5485bc945ec53e43c976b1626853ee"}, +] + [[package]] name = "cx-Oracle" version = "8.3.0" description = "Python interface to Oracle" -category = "main" optional = false python-versions = "*" +files = [ + {file = "cx_Oracle-8.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6a23da225f03f50a81980c61dbd6a358c3575f212ca7f4c22bb65a9faf94f7f"}, + {file = "cx_Oracle-8.3.0-cp310-cp310-win32.whl", hash = "sha256:715a8bbda5982af484ded14d184304cc552c1096c82471dd2948298470e88a04"}, + {file = "cx_Oracle-8.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07f01608dfb6603a8f2a868fc7c7bdc951480f187df8dbc50f4d48c884874e6a"}, + {file = "cx_Oracle-8.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4b3afe7a911cebaceda908228d36839f6441cbd38e5df491ec25960562bb01a0"}, + {file = "cx_Oracle-8.3.0-cp36-cp36m-win32.whl", hash = "sha256:076ffb71279d6b2dcbf7df028f62a01e18ce5bb73d8b01eab582bf14a62f4a61"}, + {file = "cx_Oracle-8.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b82e4b165ffd807a2bd256259a6b81b0a2452883d39f987509e2292d494ea163"}, + {file = "cx_Oracle-8.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b902db61dcdcbbf8dd981f5a46d72fef40c5150c7fc0eb0f0698b462d6eb834e"}, + {file = "cx_Oracle-8.3.0-cp37-cp37m-win32.whl", hash = "sha256:4c82ca74442c298ceec56d207450c192e06ecf8ad52eb4aaad0812e147ceabf7"}, + {file = "cx_Oracle-8.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:54164974d526b76fdefb0b66a42b68e1fca5df78713d0eeb8c1d0047b83f6bcf"}, + {file = "cx_Oracle-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:410747d542e5f94727f5f0e42e9706c772cf9094fb348ce965ab88b3a9e4d2d8"}, + {file = "cx_Oracle-8.3.0-cp38-cp38-win32.whl", hash = "sha256:3baa878597c5fadb2c72f359f548431c7be001e722ce4a4ebdf3d2293a1bb70b"}, + {file = "cx_Oracle-8.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:de42bdc882abdc5cea54597da27a05593b44143728e5b629ad5d35decb1a2036"}, + {file = "cx_Oracle-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df412238a9948340591beee9ec64fa62a2efacc0d91107034a7023e2991fba97"}, + {file = "cx_Oracle-8.3.0-cp39-cp39-win32.whl", hash = "sha256:70d3cf030aefd71f99b45beba77237b2af448adf5e26be0db3d0d3dee6ea4230"}, + {file = "cx_Oracle-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf01ce87edb4ef663b2e5bd604e1e0154d2cc2f12b60301f788b569d9db8a900"}, + {file = "cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9"}, +] [[package]] name = "dask" version = "2023.6.0" description = "Parallel PyData with Task Scheduling" -category = "main" optional = false python-versions = ">=3.9" +files = [ + {file = "dask-2023.6.0-py3-none-any.whl", hash = "sha256:294f449108397c4ca7a3bca27e32cb0fe9c65599c57d6301b3b70f825c2457d4"}, + {file = "dask-2023.6.0.tar.gz", hash = "sha256:980741966ef4d14c62dfb146f315f57d5eaaa6bedc52866f99687bc6054c2d4b"}, +] [package.dependencies] click = ">=8.0" @@ -187,9 +435,12 @@ test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailu name = "dill" version = "0.3.6" description = "serialize all of python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, + {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, +] [package.extras] graph = ["objgraph (>=1.7.2)"] @@ -198,17 +449,23 @@ graph = ["objgraph (>=1.7.2)"] name = "distlib" version = "0.3.6" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] [[package]] name = "distributed" version = "2023.6.0" description = "Distributed scheduler for Dask" -category = "main" optional = false python-versions = ">=3.9" +files = [ + {file = "distributed-2023.6.0-py3-none-any.whl", hash = "sha256:dea5c199623aab6babaa5c6a9c98542723c1436d065d28c68ac324266978701b"}, + {file = "distributed-2023.6.0.tar.gz", hash = "sha256:a5bdd4d15f3e7c8df98513b409753d46a39827a650620cc0c0c5ef2751561280"}, +] [package.dependencies] click = ">=8.0" @@ -231,9 +488,12 @@ zict = ">=2.2.0" name = "docker" version = "6.1.3" description = "A Python library for the Docker Engine API." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"}, + {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"}, +] [package.dependencies] packaging = ">=14.0" @@ -249,9 +509,12 @@ ssh = ["paramiko (>=2.4.3)"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] [package.extras] test = ["pytest (>=6)"] @@ -260,9 +523,12 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.12.2" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] [package.extras] docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] @@ -272,31 +538,55 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p name = "Fiona" version = "1.9.4.post1" description = "Fiona reads and writes spatial data files" -category = "main" optional = false python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -certifi = "*" -click = ">=8.0,<9.0" -click-plugins = ">=1.0" -cligj = ">=0.5" -six = "*" - -[package.extras] -all = ["Fiona[calc,s3,test]"] -calc = ["shapely"] -s3 = ["boto3 (>=1.3.1)"] -test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] - -[[package]] -name = "fsspec" -version = "2023.6.0" -description = "File-system specification" -category = "main" +files = [ + {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:d6483a20037db2209c8e9a0c6f1e552f807d03c8f42ed0c865ab500945a37c4d"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbe158947099a83ad16f9acd3a21f50ff01114c64e2de67805e382e6b6e0083a"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2c7b09eecee3bb074ef8aa518cd6ab30eb663c6fdd0eff3c88d454a9746eaa"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-win_amd64.whl", hash = "sha256:1da8b954f6f222c3c782bc285586ea8dd9d7e55e1bc7861da9cd772bca671660"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c671d8832287cda397621d79c5a635d52e4631f33a8f0e6fdc732a79a93cb96c"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b633a2e550e083805c638d2ab8059c283ca112aaea8241e170c012d2ee0aa905"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1faa625d5202b8403471bbc9f9c96b1bf9099cfcb0ee02a80a3641d3d02383e"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-win_amd64.whl", hash = "sha256:39baf11ff0e4318397e2b2197de427b4eebdc49d4a9a7c1366f8a7ed682978a4"}, + {file = "Fiona-1.9.4.post1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d93c993265f6378b23f47708c83bddb3377ca6814a1f0b5a0ae0bee9c8d72cf8"}, + {file = "Fiona-1.9.4.post1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b0387cae39e27f338fd948b3b50b6e6ce198cc4cec257fc91660849697c69dc3"}, + {file = "Fiona-1.9.4.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:450561d308d3ce7c7e30294822b1de3f4f942033b703ddd4a91a7f7f5f506ca0"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71b023ef5248ebfa5524e7a875033f7db3bbfaf634b1b5c1ae36958d1eb82083"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74511d3755695d75cea0f4ff6f5e0c6c5d5be8e0d46dafff124c6a219e99b1eb"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:285f3dd4f96aa0a3955ed469f0543375b20989731b2dddc85124453f11ac62bc"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-win_amd64.whl", hash = "sha256:a670ea4262cb9140445bcfc97cbfd2f508a058be342f4a97e966b8ce7696601f"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:ea7c44c15b3a653452b9b3173181490b7afc5f153b0473c145c43c0fbf90448b"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7bfb1f49e0e53f6cd7ad64ae809d72646266b37a7b9881205977408b443a8d79"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a585002a6385cc8ab0f66ddf3caf18711f531901906abd011a67a0cc89ab7b0"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-win_amd64.whl", hash = "sha256:f5da66b723a876142937e683431bbaa5c3d81bb2ed3ec98941271bc99b7f8cd0"}, + {file = "Fiona-1.9.4.post1.tar.gz", hash = "sha256:5679d3f7e0d513035eb72e59527bb90486859af4405755dfc739138633106120"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +certifi = "*" +click = ">=8.0,<9.0" +click-plugins = ">=1.0" +cligj = ">=0.5" +six = "*" + +[package.extras] +all = ["Fiona[calc,s3,test]"] +calc = ["shapely"] +s3 = ["boto3 (>=1.3.1)"] +test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] + +[[package]] +name = "fsspec" +version = "2023.6.0" +description = "File-system specification" optional = false python-versions = ">=3.8" +files = [ + {file = "fsspec-2023.6.0-py3-none-any.whl", hash = "sha256:1cbad1faef3e391fba6dc005ae9b5bdcbf43005c9167ce78c915549c352c869a"}, + {file = "fsspec-2023.6.0.tar.gz", hash = "sha256:d0b2f935446169753e7a5c5c55681c54ea91996cc67be93c39a154fb3a2742af"}, +] [package.extras] abfs = ["adlfs"] @@ -326,9 +616,12 @@ tqdm = ["tqdm"] name = "GeoAlchemy2" version = "0.13.3" description = "Using SQLAlchemy with Spatial Databases" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "GeoAlchemy2-0.13.3-py3-none-any.whl", hash = "sha256:a29f98cbe2527284d2374a0aa6f7bf31fb7babb61bc56b7e557afdb21610827a"}, + {file = "GeoAlchemy2-0.13.3.tar.gz", hash = "sha256:d85a7deaa9a230901733f7419d6bff5674b8730651de1a07f2b6423aa4925d09"}, +] [package.dependencies] packaging = "*" @@ -338,9 +631,12 @@ SQLAlchemy = ">=1.4" name = "geopandas" version = "0.13.2" description = "Geographic pandas extensions" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "geopandas-0.13.2-py3-none-any.whl", hash = "sha256:101cfd0de54bcf9e287a55b5ea17ebe0db53a5e25a28bacf100143d0507cabd9"}, + {file = "geopandas-0.13.2.tar.gz", hash = "sha256:e5b56d9c20800c77bcc0c914db3f27447a37b23b2cd892be543f5001a694a968"}, +] [package.dependencies] fiona = ">=1.8.19" @@ -353,9 +649,74 @@ shapely = ">=1.7.1" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] [package.extras] docs = ["Sphinx", "docutils (<0.18)"] @@ -365,9 +726,12 @@ test = ["objgraph", "psutil"] name = "identify" version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] [package.extras] license = ["ukkonen"] @@ -376,17 +740,23 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" 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 = "importlib-metadata" version = "6.6.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, +] [package.dependencies] zipp = ">=0.5" @@ -400,9 +770,12 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "importlib-resources" version = "5.12.0" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] @@ -412,17 +785,23 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] [[package]] name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] [package.extras] colors = ["colorama (>=0.4.3)"] @@ -434,9 +813,12 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "Jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] [package.dependencies] MarkupSafe = ">=2.0" @@ -448,33 +830,137 @@ i18n = ["Babel (>=2.7)"] name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] [[package]] name = "locket" version = "1.0.0" description = "File-based locks for Python on Linux and Windows" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"}, + {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, +] [[package]] name = "MarkupSafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] [[package]] name = "marshmallow" version = "3.19.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "marshmallow-3.19.0-py3-none-any.whl", hash = "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"}, + {file = "marshmallow-3.19.0.tar.gz", hash = "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78"}, +] [package.dependencies] packaging = ">=17.0" @@ -489,9 +975,12 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-oneofschema" version = "3.0.1" description = "marshmallow multiplexing schema" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "marshmallow-oneofschema-3.0.1.tar.gz", hash = "sha256:62cd2099b29188c92493c2940ee79d1bf2f2619a71721664e5a98ec2faa58237"}, + {file = "marshmallow_oneofschema-3.0.1-py2.py3-none-any.whl", hash = "sha256:bd29410a9f2f7457a2b428286e2a80ef76b8ddc3701527dc1f935a88914b02f2"}, +] [package.dependencies] marshmallow = ">=3.0.0,<4.0.0" @@ -505,52 +994,152 @@ tests = ["mock", "pytest"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] [[package]] name = "msgpack" version = "1.0.5" description = "MessagePack serializer" -category = "main" optional = false python-versions = "*" +files = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] + +[[package]] +name = "mypy" +version = "1.3.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, + {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, + {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, + {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, + {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, + {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, + {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, + {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, + {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, + {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, + {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, + {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, + {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, + {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, + {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, + {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, + {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, + {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, + {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] [[package]] -name = "mypy" -version = "1.3.0" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] [package.dependencies] setuptools = "*" @@ -559,25 +1148,83 @@ setuptools = "*" name = "numpy" version = "1.24.3" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, +] [[package]] name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] [[package]] name = "pandas" version = "2.0.2" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "pandas-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ebb9f1c22ddb828e7fd017ea265a59d80461d5a79154b49a4207bd17514d122"}, + {file = "pandas-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eb09a242184092f424b2edd06eb2b99d06dc07eeddff9929e8667d4ed44e181"}, + {file = "pandas-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7319b6e68de14e6209460f72a8d1ef13c09fb3d3ef6c37c1e65b35d50b5c145"}, + {file = "pandas-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd46bde7309088481b1cf9c58e3f0e204b9ff9e3244f441accd220dd3365ce7c"}, + {file = "pandas-2.0.2-cp310-cp310-win32.whl", hash = "sha256:51a93d422fbb1bd04b67639ba4b5368dffc26923f3ea32a275d2cc450f1d1c86"}, + {file = "pandas-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:66d00300f188fa5de73f92d5725ced162488f6dc6ad4cecfe4144ca29debe3b8"}, + {file = "pandas-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02755de164da6827764ceb3bbc5f64b35cb12394b1024fdf88704d0fa06e0e2f"}, + {file = "pandas-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0a1e0576611641acde15c2322228d138258f236d14b749ad9af498ab69089e2d"}, + {file = "pandas-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6b5f14cd24a2ed06e14255ff40fe2ea0cfaef79a8dd68069b7ace74bd6acbba"}, + {file = "pandas-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50e451932b3011b61d2961b4185382c92cc8c6ee4658dcd4f320687bb2d000ee"}, + {file = "pandas-2.0.2-cp311-cp311-win32.whl", hash = "sha256:7b21cb72958fc49ad757685db1919021d99650d7aaba676576c9e88d3889d456"}, + {file = "pandas-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:c4af689352c4fe3d75b2834933ee9d0ccdbf5d7a8a7264f0ce9524e877820c08"}, + {file = "pandas-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69167693cb8f9b3fc060956a5d0a0a8dbfed5f980d9fd2c306fb5b9c855c814c"}, + {file = "pandas-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:30a89d0fec4263ccbf96f68592fd668939481854d2ff9da709d32a047689393b"}, + {file = "pandas-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a18e5c72b989ff0f7197707ceddc99828320d0ca22ab50dd1b9e37db45b010c0"}, + {file = "pandas-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7376e13d28eb16752c398ca1d36ccfe52bf7e887067af9a0474de6331dd948d2"}, + {file = "pandas-2.0.2-cp38-cp38-win32.whl", hash = "sha256:6d6d10c2142d11d40d6e6c0a190b1f89f525bcf85564707e31b0a39e3b398e08"}, + {file = "pandas-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e69140bc2d29a8556f55445c15f5794490852af3de0f609a24003ef174528b79"}, + {file = "pandas-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b42b120458636a981077cfcfa8568c031b3e8709701315e2bfa866324a83efa8"}, + {file = "pandas-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f908a77cbeef9bbd646bd4b81214cbef9ac3dda4181d5092a4aa9797d1bc7774"}, + {file = "pandas-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713f2f70abcdade1ddd68fc91577cb090b3544b07ceba78a12f799355a13ee44"}, + {file = "pandas-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf3f0c361a4270185baa89ec7ab92ecaa355fe783791457077473f974f654df5"}, + {file = "pandas-2.0.2-cp39-cp39-win32.whl", hash = "sha256:598e9020d85a8cdbaa1815eb325a91cfff2bb2b23c1442549b8a3668e36f0f77"}, + {file = "pandas-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:77550c8909ebc23e56a89f91b40ad01b50c42cfbfab49b3393694a50549295ea"}, + {file = "pandas-2.0.2.tar.gz", hash = "sha256:dd5476b6c3fe410ee95926873f377b856dbc4e81a9c605a0dc05aaccc6a7c6c6"}, +] [package.dependencies] numpy = {version = ">=1.21.0", markers = "python_version >= \"3.10\""} @@ -598,7 +1245,7 @@ gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] hdf5 = ["tables (>=3.6.1)"] html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output_formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] parquet = ["pyarrow (>=7.0.0)"] performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] plot = ["matplotlib (>=3.6.1)"] @@ -612,9 +1259,12 @@ xml = ["lxml (>=4.6.3)"] name = "partd" version = "1.4.0" description = "Appendable key-value storage" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "partd-1.4.0-py3-none-any.whl", hash = "sha256:7a63529348cf0dff14b986db641cd1b83c16b5cb9fc647c2851779db03282ef8"}, + {file = "partd-1.4.0.tar.gz", hash = "sha256:aa0ff35dbbcc807ae374db56332f4c1b39b46f67bf2975f5151e0b4186aed0d5"}, +] [package.dependencies] locket = "*" @@ -627,17 +1277,42 @@ complete = ["blosc", "numpy (>=1.9.0)", "pandas (>=0.19.0)", "pyzmq"] name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] [[package]] name = "pendulum" version = "2.1.2" description = "Python datetimes made easy" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, + {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, + {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, + {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, + {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, + {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, + {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, + {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, + {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, + {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, + {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, +] [package.dependencies] python-dateutil = ">=2.6,<3.0" @@ -647,9 +1322,12 @@ pytzdata = ">=2020.1" name = "platformdirs" version = "3.5.3" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"}, + {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"}, +] [package.extras] docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] @@ -659,9 +1337,12 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.extras] dev = ["pre-commit", "tox"] @@ -671,9 +1352,12 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, + {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, +] [package.dependencies] cfgv = ">=2.0.0" @@ -686,9 +1370,12 @@ virtualenv = ">=20.10.0" name = "prefect" version = "1.4.1" description = "The Prefect Core automation and scheduling engine." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "prefect-1.4.1-py3-none-any.whl", hash = "sha256:a838d427a88845b13279b89b925e2b6acde5ff2bb090c5480617bc6047a808a8"}, + {file = "prefect-1.4.1.tar.gz", hash = "sha256:179f179849286bb8dc0309c8718a7815e6e5fcc016398d2aea45e16fa0e3471b"}, +] [package.dependencies] click = ">=7.0" @@ -716,15 +1403,15 @@ urllib3 = ">=1.26.0" [package.extras] airtable = ["airtable-python-wrapper (>=0.11)"] -all_extras = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "airtable-python-wrapper (>=0.11)", "atlassian-python-api (>=2.0.1)", "azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)", "azureml-sdk", "black", "boto3 (>=1.9)", "confluent-kafka (>=1.7.0)", "dask-cloudprovider[aws] (>=0.2.0)", "dask-kubernetes (>=0.8.0)", "dropbox (>=9.0,<10.0)", "dulwich (>=0.19.7)", "feedparser (>=5.0.1)", "firebolt-sdk (>=0.2.1)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "graphviz (>=0.8.3)", "great-expectations (>=0.13.8)", "gspread (>=3.6.0)", "hvac (>=0.10)", "ipykernel (>=6.9.2)", "jinja2 (>=2.0)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "nbconvert (>=6.0.7)", "pandas (>=1.0.1)", "papermill (>=2.2.0)", "paramiko (>=2.10.4)", "prometheus-client (>=0.9.0)", "psycopg2-binary (>=2.8.2)", "pushbullet.py (>=0.11.0)", "py2neo (>=2021.2.3)", "pyarrow (>=5.0.0)", "pydantic (>=1.9.0)", "pyexasol (>=0.16.1)", "pymysql (>=0.9.3)", "pyodbc (>=4.0.30)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "redis (>=3.2.1)", "responses (>=0.14.0)", "sendgrid (>=6.7.0)", "snowflake-connector-python (>=1.8.2)", "soda-spark (>=0.2.1)", "soda-sql (>=2.0.0b25)", "spacy (>=2.0.0)", "sqlalchemy-redshift (>=0.8.11)", "testfixtures (>=6.10.3)", "toloka-kit (>=0.1.25)", "transform (>=1.0.12)", "tweepy (>=3.5)"] -all_orchestration_extras = ["PyGithub (>=1.51)", "atlassian-python-api (>=2.0.1)", "azure-identity (>=1.7.0)", "azure-storage-blob (>=12.1.0)", "boto3 (>=1.9)", "dulwich (>=0.19.7)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "kubernetes (>=9.0.0a1)", "python-gitlab (>=2.5.0)"] +all-extras = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "airtable-python-wrapper (>=0.11)", "atlassian-python-api (>=2.0.1)", "azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)", "azureml-sdk", "black", "boto3 (>=1.9)", "confluent-kafka (>=1.7.0)", "dask-cloudprovider[aws] (>=0.2.0)", "dask-kubernetes (>=0.8.0)", "dropbox (>=9.0,<10.0)", "dulwich (>=0.19.7)", "feedparser (>=5.0.1)", "firebolt-sdk (>=0.2.1)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "graphviz (>=0.8.3)", "great-expectations (>=0.13.8)", "gspread (>=3.6.0)", "hvac (>=0.10)", "ipykernel (>=6.9.2)", "jinja2 (>=2.0)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "nbconvert (>=6.0.7)", "pandas (>=1.0.1)", "papermill (>=2.2.0)", "paramiko (>=2.10.4)", "prometheus-client (>=0.9.0)", "psycopg2-binary (>=2.8.2)", "pushbullet.py (>=0.11.0)", "py2neo (>=2021.2.3)", "pyarrow (>=5.0.0)", "pydantic (>=1.9.0)", "pyexasol (>=0.16.1)", "pymysql (>=0.9.3)", "pyodbc (>=4.0.30)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "redis (>=3.2.1)", "responses (>=0.14.0)", "sendgrid (>=6.7.0)", "snowflake-connector-python (>=1.8.2)", "soda-spark (>=0.2.1)", "soda-sql (>=2.0.0b25)", "spacy (>=2.0.0)", "sqlalchemy-redshift (>=0.8.11)", "testfixtures (>=6.10.3)", "toloka-kit (>=0.1.25)", "transform (>=1.0.12)", "tweepy (>=3.5)"] +all-orchestration-extras = ["PyGithub (>=1.51)", "atlassian-python-api (>=2.0.1)", "azure-identity (>=1.7.0)", "azure-storage-blob (>=12.1.0)", "boto3 (>=1.9)", "dulwich (>=0.19.7)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "kubernetes (>=9.0.0a1)", "python-gitlab (>=2.5.0)"] aws = ["boto3 (>=1.9)"] azure = ["azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)"] azureml = ["azureml-sdk"] -base_library_ci = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "atlassian-python-api (>=2.0.1)", "azure-identity (>=1.7.0)", "azure-storage-blob (>=12.1.0)", "black", "boto3 (>=1.9)", "dulwich (>=0.19.7)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "pandas (>=1.0.1)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] +base-library-ci = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "atlassian-python-api (>=2.0.1)", "azure-identity (>=1.7.0)", "azure-storage-blob (>=12.1.0)", "black", "boto3 (>=1.9)", "dulwich (>=0.19.7)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "pandas (>=1.0.1)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] bitbucket = ["atlassian-python-api (>=2.0.1)"] cubejs = ["PyJWT (>=2.3.0)"] -dask_cloudprovider = ["dask-cloudprovider[aws] (>=0.2.0)"] +dask-cloudprovider = ["dask-cloudprovider[aws] (>=0.2.0)"] databricks = ["pydantic (>=1.9.0)"] dev = ["PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "black", "flaky (>=3.0)", "freezegun (>=1.0.0)", "graphviz (>=0.8)", "jinja2 (>=2.0,<4.0)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] dremio = ["pyarrow (>=5.0.0)"] @@ -756,8 +1443,8 @@ snowflake = ["snowflake-connector-python (>=1.8.2)"] sodaspark = ["soda-spark (>=0.2.1)"] sodasql = ["soda-sql (>=2.0.0b25)"] spacy = ["spacy (>=2.0.0)"] -sql_server = ["pyodbc (>=4.0.30)"] -task_library_ci = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "airtable-python-wrapper (>=0.11)", "atlassian-python-api (>=2.0.1)", "azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)", "azureml-sdk", "black", "boto3 (>=1.9)", "confluent-kafka (>=1.7.0)", "dask-kubernetes (>=0.8.0)", "dropbox (>=9.0,<10.0)", "dulwich (>=0.19.7)", "feedparser (>=5.0.1)", "firebolt-sdk (>=0.2.1)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "graphviz (>=0.8.3)", "great-expectations (>=0.13.8)", "gspread (>=3.6.0)", "hvac (>=0.10)", "ipykernel (>=6.9.2)", "jinja2 (>=2.0)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "nbconvert (>=6.0.7)", "pandas (>=1.0.1)", "papermill (>=2.2.0)", "paramiko (>=2.10.4)", "prometheus-client (>=0.9.0)", "psycopg2-binary (>=2.8.2)", "pushbullet.py (>=0.11.0)", "py2neo (>=2021.2.3)", "pyarrow (>=5.0.0)", "pydantic (>=1.9.0)", "pyexasol (>=0.16.1)", "pymysql (>=0.9.3)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "redis (>=3.2.1)", "responses (>=0.14.0)", "sendgrid (>=6.7.0)", "snowflake-connector-python (>=1.8.2)", "spacy (>=2.0.0)", "sqlalchemy-redshift (>=0.8.11)", "testfixtures (>=6.10.3)", "toloka-kit (>=0.1.25)", "transform (>=1.0.12)", "tweepy (>=3.5)"] +sql-server = ["pyodbc (>=4.0.30)"] +task-library-ci = ["PyGithub (>=1.51)", "PyJWT (>=2.3.0)", "Pygments (>=2.2,<3.0)", "airtable-python-wrapper (>=0.11)", "atlassian-python-api (>=2.0.1)", "azure-core (>=1.10.0)", "azure-cosmos (>=3.1.1)", "azure-identity (>=1.7.0)", "azure-mgmt-datafactory (>=2.7.0)", "azure-storage-blob (>=12.1.0)", "azureml-sdk", "black", "boto3 (>=1.9)", "confluent-kafka (>=1.7.0)", "dask-kubernetes (>=0.8.0)", "dropbox (>=9.0,<10.0)", "dulwich (>=0.19.7)", "feedparser (>=5.0.1)", "firebolt-sdk (>=0.2.1)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "google-auth (>=2.0)", "google-cloud-aiplatform (>=1.4.0)", "google-cloud-bigquery (>=1.6.0)", "google-cloud-secret-manager (>=2.4.0)", "google-cloud-storage (>=1.13)", "graphviz (>=0.8)", "graphviz (>=0.8.3)", "great-expectations (>=0.13.8)", "gspread (>=3.6.0)", "hvac (>=0.10)", "ipykernel (>=6.9.2)", "jinja2 (>=2.0)", "jinja2 (>=2.0,<4.0)", "jira (>=2.0.0)", "kubernetes (>=9.0.0a1)", "nbconvert (>=6.0.7)", "pandas (>=1.0.1)", "papermill (>=2.2.0)", "paramiko (>=2.10.4)", "prometheus-client (>=0.9.0)", "psycopg2-binary (>=2.8.2)", "pushbullet.py (>=0.11.0)", "py2neo (>=2021.2.3)", "pyarrow (>=5.0.0)", "pydantic (>=1.9.0)", "pyexasol (>=0.16.1)", "pymysql (>=0.9.3)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "python-gitlab (>=2.5.0)", "redis (>=3.2.1)", "responses (>=0.14.0)", "sendgrid (>=6.7.0)", "snowflake-connector-python (>=1.8.2)", "spacy (>=2.0.0)", "sqlalchemy-redshift (>=0.8.11)", "testfixtures (>=6.10.3)", "toloka-kit (>=0.1.25)", "transform (>=1.0.12)", "tweepy (>=3.5)"] templates = ["jinja2 (>=2.0)"] test = ["PyJWT (>=2.3.0)", "flaky (>=3.0)", "freezegun (>=1.0.0)", "pytest (>=6.0)", "pytest-env (>=0.6.0)", "pytest-xdist (>=2.0)", "responses (>=0.14.0)", "testfixtures (>=6.10.3)"] toloka = ["toloka-kit (>=0.1.25)"] @@ -770,9 +1457,24 @@ viz = ["graphviz (>=0.8.3)"] name = "psutil" version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +] [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] @@ -781,1122 +1483,9 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" - -[[package]] -name = "pylint" -version = "2.17.4" -description = "python code static checker" -category = "dev" -optional = false -python-versions = ">=3.7.2" - -[package.dependencies] -astroid = ">=2.15.4,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = {version = ">=0.2", markers = "python_version < \"3.11\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pyproj" -version = "3.6.0" -description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" -category = "main" -optional = false -python-versions = ">=3.9" - -[package.dependencies] -certifi = "*" - -[[package]] -name = "pytest" -version = "7.3.2" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-box" -version = "7.0.1" -description = "Advanced Python dictionaries with dot notation access" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"] -msgpack = ["msgpack"] -pyyaml = ["PyYAML"] -"ruamel.yaml" = ["ruamel.yaml (>=0.17)"] -toml = ["toml"] -tomli = ["tomli", "tomli-w"] -yaml = ["ruamel.yaml (>=0.17)"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-slugify" -version = "8.0.1" -description = "A Python slugify application that also handles Unicode" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -text-unidecode = ">=1.3" - -[package.extras] -unidecode = ["Unidecode (>=1.1.1)"] - -[[package]] -name = "pytz" -version = "2023.3" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pytzdata" -version = "2020.1" -description = "The Olson timezone database for Python." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "PyYAML" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "setuptools" -version = "67.8.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "shapely" -version = "2.0.1" -description = "Manipulation and analysis of geometric objects" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -numpy = ">=1.14" - -[package.extras] -docs = ["matplotlib", "numpydoc (>=1.1.0,<1.2.0)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "simplejson" -version = "3.19.1" -description = "Simple, fast, extensible JSON encoder/decoder for Python" -category = "main" -optional = false -python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "sortedcontainers" -version = "2.4.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "SQLAlchemy" -version = "2.0.16" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.2.0" - -[package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql_pymssql = ["pymssql"] -mssql_pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql_connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] -oracle_oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql_pg8000 = ["pg8000 (>=1.29.1)"] -postgresql_psycopg = ["psycopg (>=3.0.7)"] -postgresql_psycopg2binary = ["psycopg2-binary"] -postgresql_psycopg2cffi = ["psycopg2cffi"] -postgresql_psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "tblib" -version = "1.7.0" -description = "Traceback serialization library." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "text-unidecode" -version = "1.3" -description = "The most basic Text::Unidecode port" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tomlkit" -version = "0.11.8" -description = "Style preserving TOML library" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "toolz" -version = "0.12.0" -description = "List processing tools and functional utilities" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "tornado" -version = "6.3.2" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "main" -optional = false -python-versions = ">= 3.8" - -[[package]] -name = "typing-extensions" -version = "4.6.3" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tzdata" -version = "2023.3" -description = "Provider of IANA time zone data" -category = "main" -optional = false -python-versions = ">=2" - -[[package]] -name = "urllib3" -version = "2.0.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "virtualenv" -version = "20.23.0" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.11,<4" -platformdirs = ">=3.2,<4" - -[package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] - -[[package]] -name = "websocket-client" -version = "1.5.3" -description = "WebSocket client for Python with low level API options" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "wrapt" -version = "1.15.0" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "zict" -version = "3.0.0" -description = "Mutable mapping tools" -category = "main" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = "3.10.12" -content-hash = "d584ebf2beccb96032dc8228a88d272ee7fddeb29313372ff6d64589843228d6" - -[metadata.files] -astroid = [ - {file = "astroid-2.15.5-py3-none-any.whl", hash = "sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324"}, - {file = "astroid-2.15.5.tar.gz", hash = "sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"}, -] -attrs = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] -black = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, -] -certifi = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -click-plugins = [ - {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, - {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, -] -cligj = [ - {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, - {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, -] -cloudpickle = [ - {file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"}, - {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, -] -croniter = [ - {file = "croniter-1.4.0-py2.py3-none-any.whl", hash = "sha256:4cb177d0167d4a61b1aa3575fcf06b9a064e9a48a4b11d6ab474b1b9e126ed9c"}, - {file = "croniter-1.4.0.tar.gz", hash = "sha256:edf332f2ef3b2b47d4c01004c640981c5a70d8ccb6b854aea8866a28b532449a"}, -] -cx-Oracle = [ - {file = "cx_Oracle-8.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6a23da225f03f50a81980c61dbd6a358c3575f212ca7f4c22bb65a9faf94f7f"}, - {file = "cx_Oracle-8.3.0-cp310-cp310-win32.whl", hash = "sha256:715a8bbda5982af484ded14d184304cc552c1096c82471dd2948298470e88a04"}, - {file = "cx_Oracle-8.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07f01608dfb6603a8f2a868fc7c7bdc951480f187df8dbc50f4d48c884874e6a"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4b3afe7a911cebaceda908228d36839f6441cbd38e5df491ec25960562bb01a0"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-win32.whl", hash = "sha256:076ffb71279d6b2dcbf7df028f62a01e18ce5bb73d8b01eab582bf14a62f4a61"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b82e4b165ffd807a2bd256259a6b81b0a2452883d39f987509e2292d494ea163"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b902db61dcdcbbf8dd981f5a46d72fef40c5150c7fc0eb0f0698b462d6eb834e"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-win32.whl", hash = "sha256:4c82ca74442c298ceec56d207450c192e06ecf8ad52eb4aaad0812e147ceabf7"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:54164974d526b76fdefb0b66a42b68e1fca5df78713d0eeb8c1d0047b83f6bcf"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:410747d542e5f94727f5f0e42e9706c772cf9094fb348ce965ab88b3a9e4d2d8"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-win32.whl", hash = "sha256:3baa878597c5fadb2c72f359f548431c7be001e722ce4a4ebdf3d2293a1bb70b"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:de42bdc882abdc5cea54597da27a05593b44143728e5b629ad5d35decb1a2036"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df412238a9948340591beee9ec64fa62a2efacc0d91107034a7023e2991fba97"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-win32.whl", hash = "sha256:70d3cf030aefd71f99b45beba77237b2af448adf5e26be0db3d0d3dee6ea4230"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf01ce87edb4ef663b2e5bd604e1e0154d2cc2f12b60301f788b569d9db8a900"}, - {file = "cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9"}, -] -dask = [ - {file = "dask-2023.6.0-py3-none-any.whl", hash = "sha256:294f449108397c4ca7a3bca27e32cb0fe9c65599c57d6301b3b70f825c2457d4"}, - {file = "dask-2023.6.0.tar.gz", hash = "sha256:980741966ef4d14c62dfb146f315f57d5eaaa6bedc52866f99687bc6054c2d4b"}, -] -dill = [ - {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, - {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, -] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] -distributed = [ - {file = "distributed-2023.6.0-py3-none-any.whl", hash = "sha256:dea5c199623aab6babaa5c6a9c98542723c1436d065d28c68ac324266978701b"}, - {file = "distributed-2023.6.0.tar.gz", hash = "sha256:a5bdd4d15f3e7c8df98513b409753d46a39827a650620cc0c0c5ef2751561280"}, -] -docker = [ - {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"}, - {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, -] -filelock = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, -] -Fiona = [ - {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:d6483a20037db2209c8e9a0c6f1e552f807d03c8f42ed0c865ab500945a37c4d"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbe158947099a83ad16f9acd3a21f50ff01114c64e2de67805e382e6b6e0083a"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2c7b09eecee3bb074ef8aa518cd6ab30eb663c6fdd0eff3c88d454a9746eaa"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-win_amd64.whl", hash = "sha256:1da8b954f6f222c3c782bc285586ea8dd9d7e55e1bc7861da9cd772bca671660"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c671d8832287cda397621d79c5a635d52e4631f33a8f0e6fdc732a79a93cb96c"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b633a2e550e083805c638d2ab8059c283ca112aaea8241e170c012d2ee0aa905"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1faa625d5202b8403471bbc9f9c96b1bf9099cfcb0ee02a80a3641d3d02383e"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-win_amd64.whl", hash = "sha256:39baf11ff0e4318397e2b2197de427b4eebdc49d4a9a7c1366f8a7ed682978a4"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d93c993265f6378b23f47708c83bddb3377ca6814a1f0b5a0ae0bee9c8d72cf8"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b0387cae39e27f338fd948b3b50b6e6ce198cc4cec257fc91660849697c69dc3"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:450561d308d3ce7c7e30294822b1de3f4f942033b703ddd4a91a7f7f5f506ca0"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71b023ef5248ebfa5524e7a875033f7db3bbfaf634b1b5c1ae36958d1eb82083"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74511d3755695d75cea0f4ff6f5e0c6c5d5be8e0d46dafff124c6a219e99b1eb"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:285f3dd4f96aa0a3955ed469f0543375b20989731b2dddc85124453f11ac62bc"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-win_amd64.whl", hash = "sha256:a670ea4262cb9140445bcfc97cbfd2f508a058be342f4a97e966b8ce7696601f"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:ea7c44c15b3a653452b9b3173181490b7afc5f153b0473c145c43c0fbf90448b"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7bfb1f49e0e53f6cd7ad64ae809d72646266b37a7b9881205977408b443a8d79"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a585002a6385cc8ab0f66ddf3caf18711f531901906abd011a67a0cc89ab7b0"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-win_amd64.whl", hash = "sha256:f5da66b723a876142937e683431bbaa5c3d81bb2ed3ec98941271bc99b7f8cd0"}, - {file = "Fiona-1.9.4.post1.tar.gz", hash = "sha256:5679d3f7e0d513035eb72e59527bb90486859af4405755dfc739138633106120"}, -] -fsspec = [ - {file = "fsspec-2023.6.0-py3-none-any.whl", hash = "sha256:1cbad1faef3e391fba6dc005ae9b5bdcbf43005c9167ce78c915549c352c869a"}, - {file = "fsspec-2023.6.0.tar.gz", hash = "sha256:d0b2f935446169753e7a5c5c55681c54ea91996cc67be93c39a154fb3a2742af"}, -] -GeoAlchemy2 = [ - {file = "GeoAlchemy2-0.13.3-py3-none-any.whl", hash = "sha256:a29f98cbe2527284d2374a0aa6f7bf31fb7babb61bc56b7e557afdb21610827a"}, - {file = "GeoAlchemy2-0.13.3.tar.gz", hash = "sha256:d85a7deaa9a230901733f7419d6bff5674b8730651de1a07f2b6423aa4925d09"}, -] -geopandas = [ - {file = "geopandas-0.13.2-py3-none-any.whl", hash = "sha256:101cfd0de54bcf9e287a55b5ea17ebe0db53a5e25a28bacf100143d0507cabd9"}, - {file = "geopandas-0.13.2.tar.gz", hash = "sha256:e5b56d9c20800c77bcc0c914db3f27447a37b23b2cd892be543f5001a694a968"}, -] -greenlet = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, -] -identify = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, - {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, -] -importlib-resources = [ - {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, - {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, -] -iniconfig = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] -isort = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, -] -Jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, -] -locket = [ - {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"}, - {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, -] -MarkupSafe = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] -marshmallow = [ - {file = "marshmallow-3.19.0-py3-none-any.whl", hash = "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"}, - {file = "marshmallow-3.19.0.tar.gz", hash = "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78"}, -] -marshmallow-oneofschema = [ - {file = "marshmallow-oneofschema-3.0.1.tar.gz", hash = "sha256:62cd2099b29188c92493c2940ee79d1bf2f2619a71721664e5a98ec2faa58237"}, - {file = "marshmallow_oneofschema-3.0.1-py2.py3-none-any.whl", hash = "sha256:bd29410a9f2f7457a2b428286e2a80ef76b8ddc3701527dc1f935a88914b02f2"}, -] -mccabe = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] -msgpack = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, -] -mypy = [ - {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, - {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, - {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, - {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, - {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, - {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, - {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, - {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, - {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, - {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, - {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, - {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, - {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, - {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, - {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, - {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, - {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, - {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, - {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, - {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, - {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, - {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, - {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, - {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, - {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, - {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, -] -mypy-extensions = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] -nodeenv = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] -numpy = [ - {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, - {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, - {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, - {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, - {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, - {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, - {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, - {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, - {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, - {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, - {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, - {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, - {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, - {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, - {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, - {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, - {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, - {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, - {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, - {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, - {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, - {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, - {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, - {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, - {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, - {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, - {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, - {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, -] -packaging = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, -] -pandas = [ - {file = "pandas-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ebb9f1c22ddb828e7fd017ea265a59d80461d5a79154b49a4207bd17514d122"}, - {file = "pandas-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eb09a242184092f424b2edd06eb2b99d06dc07eeddff9929e8667d4ed44e181"}, - {file = "pandas-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7319b6e68de14e6209460f72a8d1ef13c09fb3d3ef6c37c1e65b35d50b5c145"}, - {file = "pandas-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd46bde7309088481b1cf9c58e3f0e204b9ff9e3244f441accd220dd3365ce7c"}, - {file = "pandas-2.0.2-cp310-cp310-win32.whl", hash = "sha256:51a93d422fbb1bd04b67639ba4b5368dffc26923f3ea32a275d2cc450f1d1c86"}, - {file = "pandas-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:66d00300f188fa5de73f92d5725ced162488f6dc6ad4cecfe4144ca29debe3b8"}, - {file = "pandas-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02755de164da6827764ceb3bbc5f64b35cb12394b1024fdf88704d0fa06e0e2f"}, - {file = "pandas-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0a1e0576611641acde15c2322228d138258f236d14b749ad9af498ab69089e2d"}, - {file = "pandas-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6b5f14cd24a2ed06e14255ff40fe2ea0cfaef79a8dd68069b7ace74bd6acbba"}, - {file = "pandas-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50e451932b3011b61d2961b4185382c92cc8c6ee4658dcd4f320687bb2d000ee"}, - {file = "pandas-2.0.2-cp311-cp311-win32.whl", hash = "sha256:7b21cb72958fc49ad757685db1919021d99650d7aaba676576c9e88d3889d456"}, - {file = "pandas-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:c4af689352c4fe3d75b2834933ee9d0ccdbf5d7a8a7264f0ce9524e877820c08"}, - {file = "pandas-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69167693cb8f9b3fc060956a5d0a0a8dbfed5f980d9fd2c306fb5b9c855c814c"}, - {file = "pandas-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:30a89d0fec4263ccbf96f68592fd668939481854d2ff9da709d32a047689393b"}, - {file = "pandas-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a18e5c72b989ff0f7197707ceddc99828320d0ca22ab50dd1b9e37db45b010c0"}, - {file = "pandas-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7376e13d28eb16752c398ca1d36ccfe52bf7e887067af9a0474de6331dd948d2"}, - {file = "pandas-2.0.2-cp38-cp38-win32.whl", hash = "sha256:6d6d10c2142d11d40d6e6c0a190b1f89f525bcf85564707e31b0a39e3b398e08"}, - {file = "pandas-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e69140bc2d29a8556f55445c15f5794490852af3de0f609a24003ef174528b79"}, - {file = "pandas-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b42b120458636a981077cfcfa8568c031b3e8709701315e2bfa866324a83efa8"}, - {file = "pandas-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f908a77cbeef9bbd646bd4b81214cbef9ac3dda4181d5092a4aa9797d1bc7774"}, - {file = "pandas-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713f2f70abcdade1ddd68fc91577cb090b3544b07ceba78a12f799355a13ee44"}, - {file = "pandas-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf3f0c361a4270185baa89ec7ab92ecaa355fe783791457077473f974f654df5"}, - {file = "pandas-2.0.2-cp39-cp39-win32.whl", hash = "sha256:598e9020d85a8cdbaa1815eb325a91cfff2bb2b23c1442549b8a3668e36f0f77"}, - {file = "pandas-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:77550c8909ebc23e56a89f91b40ad01b50c42cfbfab49b3393694a50549295ea"}, - {file = "pandas-2.0.2.tar.gz", hash = "sha256:dd5476b6c3fe410ee95926873f377b856dbc4e81a9c605a0dc05aaccc6a7c6c6"}, -] -partd = [ - {file = "partd-1.4.0-py3-none-any.whl", hash = "sha256:7a63529348cf0dff14b986db641cd1b83c16b5cb9fc647c2851779db03282ef8"}, - {file = "partd-1.4.0.tar.gz", hash = "sha256:aa0ff35dbbcc807ae374db56332f4c1b39b46f67bf2975f5151e0b4186aed0d5"}, -] -pathspec = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, -] -pendulum = [ - {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, - {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, - {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, - {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, - {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, - {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, - {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, - {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, - {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, - {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, - {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, - {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, - {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, - {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, - {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, - {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, - {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, - {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, - {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, - {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, - {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, -] -platformdirs = [ - {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"}, - {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pre-commit = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, -] -prefect = [ - {file = "prefect-1.4.1-py3-none-any.whl", hash = "sha256:a838d427a88845b13279b89b925e2b6acde5ff2bb090c5480617bc6047a808a8"}, - {file = "prefect-1.4.1.tar.gz", hash = "sha256:179f179849286bb8dc0309c8718a7815e6e5fcc016398d2aea45e16fa0e3471b"}, -] -psutil = [ - {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, - {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, - {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, - {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, - {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, - {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, - {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, - {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, -] -psycopg2-binary = [ +files = [ {file = "psycopg2-binary-2.9.6.tar.gz", hash = "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c"}, {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7"}, {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365"}, @@ -1960,11 +1549,39 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.6-cp39-cp39-win32.whl", hash = "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848"}, {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"}, ] -pylint = [ + +[[package]] +name = "pylint" +version = "2.17.4" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, ] -pyproj = [ + +[package.dependencies] +astroid = ">=2.15.4,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.2", markers = "python_version < \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyproj" +version = "3.6.0" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +optional = false +python-versions = ">=3.9" +files = [ {file = "pyproj-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e600f6a2771d3b41aeb2cc1efd96771ae9a01451013da1dd48ff272e7c6e34ef"}, {file = "pyproj-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7f6cd045df29aae960391dfe06a575c110af598f1dea5add8be6ca42332b0f5"}, {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:557e6592855111c84eda176ddf6b130f55d5e2b9cb1c017b8c91b69f37f474f5"}, @@ -1991,11 +1608,39 @@ pyproj = [ {file = "pyproj-3.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a4d2d438b007cb1f8d5f6f308d53d7ff9a2508cff8f9da6e2a93b76ffd98aaf"}, {file = "pyproj-3.6.0.tar.gz", hash = "sha256:a5b111865b3f0f8b77b3983f2fbe4dd6248fc09d3730295949977c8dcd988062"}, ] -pytest = [ + +[package.dependencies] +certifi = "*" + +[[package]] +name = "pytest" +version = "7.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, ] -python-box = [ + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-box" +version = "7.0.1" +description = "Advanced Python dictionaries with dot notation access" +optional = false +python-versions = ">=3.7" +files = [ {file = "python-box-7.0.1.tar.gz", hash = "sha256:dc6724f88255ccbc07092abd506281439cc2b75c6569c754ffc2b22580e7ae06"}, {file = "python_box-7.0.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:92093bf5b1500004e3608b3f39e54230b21301ffb00ff5f4dbf775f2a409a7a9"}, {file = "python_box-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cccb85f9cbf64eda51c773f3db68aa60291ec1356b0774c37bcd4e13f26b90e7"}, @@ -2013,27 +1658,90 @@ python-box = [ {file = "python_box-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f9254a1c7ec5e729e256f9a223773aa8a76d7a995f4d6be0015f74ab93a0f97c"}, {file = "python_box-7.0.1-py3-none-any.whl", hash = "sha256:f26f210b2257fd973413e75e8b91aaff9c95d7d61266426a7d3df473c4adcfd4"}, ] -python-dateutil = [ + +[package.extras] +all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"] +msgpack = ["msgpack"] +pyyaml = ["PyYAML"] +ruamel-yaml = ["ruamel.yaml (>=0.17)"] +toml = ["toml"] +tomli = ["tomli", "tomli-w"] +yaml = ["ruamel.yaml (>=0.17)"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-dotenv = [ + +[package.dependencies] +six = ">=1.5" + +[[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"}, ] -python-slugify = [ + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-slugify" +version = "8.0.1" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, ] -pytz = [ + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] -pytzdata = [ + +[[package]] +name = "pytzdata" +version = "2020.1" +description = "The Olson timezone database for Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, ] -pywin32 = [ + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, @@ -2049,7 +1757,14 @@ pywin32 = [ {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] -PyYAML = [ + +[[package]] +name = "PyYAML" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, @@ -2091,15 +1806,51 @@ PyYAML = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -requests = [ + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] -setuptools = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "67.8.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, ] -shapely = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shapely" +version = "2.0.1" +description = "Manipulation and analysis of geometric objects" +optional = false +python-versions = ">=3.7" +files = [ {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b06d031bc64149e340448fea25eee01360a58936c89985cf584134171e05863f"}, {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a6ac34c16f4d5d3c174c76c9d7614ec8fe735f8f82b6cc97a46b54f386a86bf"}, {file = "shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:865bc3d7cc0ea63189d11a0b1120d1307ed7a64720a8bfa5be2fde5fc6d0d33f"}, @@ -2139,7 +1890,21 @@ shapely = [ {file = "shapely-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:bca57b683e3d94d0919e2f31e4d70fdfbb7059650ef1b431d9f4e045690edcd5"}, {file = "shapely-2.0.1.tar.gz", hash = "sha256:66a6b1a3e72ece97fc85536a281476f9b7794de2e646ca8a4517e2e3c1446893"}, ] -simplejson = [ + +[package.dependencies] +numpy = ">=1.14" + +[package.extras] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "simplejson" +version = "3.19.1" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "simplejson-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:412e58997a30c5deb8cab5858b8e2e5b40ca007079f7010ee74565cc13d19665"}, {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e765b1f47293dedf77946f0427e03ee45def2862edacd8868c6cf9ab97c8afbd"}, {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3231100edee292da78948fa0a77dee4e5a94a0a60bcba9ed7a9dc77f4d4bb11e"}, @@ -2226,15 +1991,36 @@ simplejson = [ {file = "simplejson-3.19.1-py3-none-any.whl", hash = "sha256:4710806eb75e87919b858af0cba4ffedc01b463edc3982ded7b55143f39e41e1"}, {file = "simplejson-3.19.1.tar.gz", hash = "sha256:6277f60848a7d8319d27d2be767a7546bc965535b28070e310b3a9af90604a4c"}, ] -six = [ + +[[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"}, ] -sortedcontainers = [ + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] -SQLAlchemy = [ + +[[package]] +name = "SQLAlchemy" +version = "2.0.16" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ {file = "SQLAlchemy-2.0.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7641f6ed2682de84d77c4894cf2e43700f3cf7a729361d7f9cac98febf3d8614"}, {file = "SQLAlchemy-2.0.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d3cbdb2f07fb0e4b897dc1df39166735e194fb946f28f26f4c9f9801c8b24f7"}, {file = "SQLAlchemy-2.0.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08a791c75d6154d46914d1e23bd81d9455f2950ec1de81f2723848c593d2c8b"}, @@ -2277,35 +2063,122 @@ SQLAlchemy = [ {file = "SQLAlchemy-2.0.16-py3-none-any.whl", hash = "sha256:53081c6fce0d49bb36d05f12dc87e008c9b0df58a163b792c5fc4ac638925f98"}, {file = "SQLAlchemy-2.0.16.tar.gz", hash = "sha256:1e2caba78e7d1f5003e88817b7a1754d4e58f4a8f956dc423bf8e304c568ab09"}, ] -tabulate = [ + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] -tblib = [ + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tblib" +version = "1.7.0" +description = "Traceback serialization library." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "tblib-1.7.0-py2.py3-none-any.whl", hash = "sha256:289fa7359e580950e7d9743eab36b0691f0310fce64dee7d9c31065b8f723e23"}, {file = "tblib-1.7.0.tar.gz", hash = "sha256:059bd77306ea7b419d4f76016aef6d7027cc8a0785579b5aad198803435f882c"}, ] -text-unidecode = [ + +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] -toml = [ + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tomli = [ + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -tomlkit = [ + +[[package]] +name = "tomlkit" +version = "0.11.8" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] -toolz = [ + +[[package]] +name = "toolz" +version = "0.12.0" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.5" +files = [ {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, ] -tornado = [ + +[[package]] +name = "tornado" +version = "6.3.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"}, {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"}, {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"}, @@ -2318,27 +2191,89 @@ tornado = [ {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"}, {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "4.6.3" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, ] -tzdata = [ + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "2.0.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, ] -virtualenv = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.23.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"}, {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"}, ] -websocket-client = [ + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.11,<4" +platformdirs = ">=3.2,<4" + +[package.extras] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] + +[[package]] +name = "websocket-client" +version = "1.5.3" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.7" +files = [ {file = "websocket-client-1.5.3.tar.gz", hash = "sha256:b96f3bce3e54e3486ebe6504bc22bd4c140392bd2eb71764db29be8f2639aa65"}, {file = "websocket_client-1.5.3-py3-none-any.whl", hash = "sha256:3566f8467cd350874c4913816355642a4942f6c1ed1e9406e3d42fae6d6c072a"}, ] -wrapt = [ + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, @@ -2415,11 +2350,34 @@ wrapt = [ {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] -zict = [ + +[[package]] +name = "zict" +version = "3.0.0" +description = "Mutable mapping tools" +optional = false +python-versions = ">=3.8" +files = [ {file = "zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae"}, {file = "zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5"}, ] -zipp = [ + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.7" +files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "3.10.12" +content-hash = "1528be75bbc581df6b3bbf613246051c5030265c8257902fc0ddb55ca1bf0a27" diff --git a/datascience/pyproject.toml b/datascience/pyproject.toml index 75bc621d8..91075c571 100644 --- a/datascience/pyproject.toml +++ b/datascience/pyproject.toml @@ -18,6 +18,7 @@ pytest = "^7.3.1" geopandas = "^0.13.2" SQLAlchemy = "^2.0.16" psycopg2-binary = "^2.9.6" +css-inline = "^0.13.0" [tool.poetry.group.dev.dependencies] # Linting diff --git a/datascience/src/pipeline/emails/stylesheets/splendid.css b/datascience/src/pipeline/emails/stylesheets/splendid.css new file mode 100644 index 000000000..9ac0caff0 --- /dev/null +++ b/datascience/src/pipeline/emails/stylesheets/splendid.css @@ -0,0 +1,128 @@ +html { + font-size: 16px; +} + +body { + line-height: 1.85; + color: #444; + font-family: 'Open Sans', Helvetica, sans-serif; + font-weight: 300; + margin: 6rem auto 1rem; + max-width: 54rem; + /* text-align: center; */ +} + +p { + color: #777; + font-size: 1rem; + margin-bottom: 1.3rem; +} + +h1, +h2, +h3, +h4 { + margin: 1.414rem 0 .5rem; + font-weight: inherit; + line-height: 1.42; +} + +h1 { + margin-top: 0; + font-size: 3.998rem; +} + +h2 { + font-size: 2.827rem; +} + +h3 { + font-size: 1.999rem; +} + +h4 { + font-size: 1.414rem; +} + +h5 { + font-size: 1.121rem; +} + +h6 { + font-size: .88rem; +} + +strong { + font-weight: bold; +} + +a, +a:visited { + color: #3498db; +} + +a:hover, +a:focus, +a:active { + color: #2980b9; +} + +pre { + background-color: #fafafa; + padding: 1rem; + text-align: left; +} + +blockquote { + margin: 0; + border-left: 5px solid #7a7a7a; + font-style: italic; + padding: 1.33em; + text-align: left; +} + +ul, +ol, +li { + text-align: left; +} + +table { + border-collapse: collapse; + margin: 25px 0; + font-size: 1em; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + margin-left: auto; + margin-right: auto; +} + +thead tr { + background-color: rgb(59, 69, 89); + color: #ffffff; +} + +th, td { + padding: 12px 15px; +} + +tbody tr { + border-bottom: 1px solid #dddddd; +} + +tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +tbody tr:last-of-type { + border-bottom: 2px solid rgb(59, 69, 89); +} + +tbody tr:hover { + background-color: #e2e2e2; +} + +.no-vertical-margin, .no-vertical-margin * { + margin-top: 0; + margin-bottom: 0; +} \ No newline at end of file diff --git a/datascience/src/pipeline/emails/templates/email_actions_to_units.jinja b/datascience/src/pipeline/emails/templates/email_actions_to_units.jinja new file mode 100644 index 000000000..1c23137c5 --- /dev/null +++ b/datascience/src/pipeline/emails/templates/email_actions_to_units.jinja @@ -0,0 +1,32 @@ + + + + Bilan hebdomadaire contrôle de l'environnement marin + + + + +
+

Bilan hebdomadaire contrôle de l'environnement marin

+
+
+
+
+

Bonjour,

+

Vous trouverez ci-joint les données des contrôles et des surveillances de l'environnement marin auquels + votre unité ({{ control_unit_name }}) a participé entre le {{ from_date }} et le {{ to_date }} qui ont été prises en compte + par le Centre d'Appui au Contrôle de l'Environnement Marin (CACEM).

+

Seuls les contrôles et surveillances des missions clôturées sont transmis dans ce bilan hebdomadaire. Si votre unité est engagée + dans une mission qui court sur plusieurs jours et qui n'était pas clôturée au {{ to_date }}, il est normal que les données + correspondantes n'y figurent pas.

+

Si des données sont manquantes, incorrectes ou incomplètes, n'hésitez pas à contacter le CACEM.

+
+
+
+
+

Centre d'Appui au Contrôle de l'Environnement Marin - Tél : +33 2 90 74 32 55

+
+ + \ No newline at end of file diff --git a/datascience/src/pipeline/entities/__init__.py b/datascience/src/pipeline/entities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datascience/src/pipeline/entities/actions_emailing.py b/datascience/src/pipeline/entities/actions_emailing.py new file mode 100644 index 000000000..de8f29d77 --- /dev/null +++ b/datascience/src/pipeline/entities/actions_emailing.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import List + +import pandas as pd + +from src.pipeline.helpers.dates import Period + + +@dataclass +class ControlUnit: + control_unit_id: int + control_unit_name: str + email_addresses: List[str] + + +@dataclass +class ControlUnitActions: + """ + Control unit and its fisheries control and environement control actions between two dates. + """ + + control_unit: ControlUnit + period: Period + env_actions: pd.DataFrame + + +@dataclass +class ControlUnitActionsSentMessage: + control_unit_id: int + control_unit_name: str + email_address: str + sending_datetime_utc: datetime + actions_min_datetime_utc: datetime + actions_max_datetime_utc: datetime + number_of_actions: int + success: bool + error_code: int + error_message: str diff --git a/datascience/src/pipeline/flows/email_actions_to_units.py b/datascience/src/pipeline/flows/email_actions_to_units.py new file mode 100644 index 000000000..576bbac0e --- /dev/null +++ b/datascience/src/pipeline/flows/email_actions_to_units.py @@ -0,0 +1,371 @@ +from datetime import datetime, timedelta +from email.message import EmailMessage +from io import BytesIO +from pathlib import Path +from smtplib import ( + SMTPDataError, + SMTPHeloError, + SMTPNotSupportedError, + SMTPRecipientsRefused, + SMTPSenderRefused, +) +from time import sleep +from typing import List + +import css_inline +import pandas as pd +import prefect +from jinja2 import Environment, FileSystemLoader, Template, select_autoescape +from prefect import Flow, Parameter, case, flatten, task, unmapped +from prefect.executors import LocalDaskExecutor + +from config import ( + CACEM_EMAIL_ADDRESS, + EMAIL_STYLESHEETS_LOCATION, + EMAIL_TEMPLATES_LOCATION, +) +from src.pipeline.entities.actions_emailing import ( + ControlUnit, + ControlUnitActions, + ControlUnitActionsSentMessage, +) +from src.pipeline.generic_tasks import extract, load +from src.pipeline.helpers.dates import Period +from src.pipeline.helpers.emails import create_html_email, send_email +from src.pipeline.shared_tasks.control_flow import ( + check_flow_not_running, + filter_results, +) +from src.pipeline.shared_tasks.dates import get_utcnow + + +@task(checkpoint=False) +def get_actions_period( + utcnow: datetime, + start_days_ago: int, + end_days_ago: int, +) -> Period: + + assert isinstance(start_days_ago, int) + assert isinstance(end_days_ago, int) + assert start_days_ago > end_days_ago + + today = utcnow.date() + + start_day = today - timedelta(days=start_days_ago) + end_day = today - timedelta( + days=end_days_ago - 1 + ) # -1 to include the last day + + return Period( + start=datetime( + year=start_day.year, month=start_day.month, day=start_day.day + ), + end=datetime(year=end_day.year, month=end_day.month, day=end_day.day), + ) + + +@task(checkpoint=False) +def extract_env_actions(period: Period): + return extract( + "monitorenv_remote", + "monitorenv/env_actions_to_email.sql", + params={ + "min_datetime_utc": period.start, + "max_datetime_utc": period.end, + }, + ) + + +@task(checkpoint=False) +def get_control_unit_ids(env_action: pd.DataFrame) -> List[int]: + # Warning : using `set` and not `.unique()` on `control_unit_id ` in order to return + # `int` and not `numpy.int64` values, which are not handled by psycopg2 when passed + # as query parameters. + return sorted(set(env_action.control_unit_id)) + + +@task(checkpoint=False) +def extract_control_units(control_unit_ids: List[str]) -> pd.DataFrame: + return extract( + "monitorenv_remote", + "monitorenv/control_units.sql", + params={ + "control_unit_ids": tuple(control_unit_ids), + }, + ) + + +@task(checkpoint=False) +def to_control_unit_actions( + env_actions: pd.DataFrame, + period: Period, + control_units: pd.DataFrame, +) -> List[ControlUnitActions]: + + records = control_units.to_dict(orient="records") + control_units = [ControlUnit(**control_unit) for control_unit in records] + + return [ + ControlUnitActions( + control_unit=control_unit, + period=period, + env_actions=env_actions[ + env_actions.control_unit_id == control_unit.control_unit_id + ].reset_index(drop=True), + ) + for control_unit in control_units + ] + + +@task(checkpoint=False) +def get_template() -> Template: + templates_locations = [ + EMAIL_TEMPLATES_LOCATION, + EMAIL_STYLESHEETS_LOCATION, + ] + + env = Environment( + loader=FileSystemLoader(templates_locations), + autoescape=select_autoescape(), + ) + + return env.get_template("email_actions_to_units.jinja") + + +@task(checkpoint=False) +def render(actions: ControlUnitActions, template: Template) -> str: + + html = template.render( + control_unit_name=actions.control_unit.control_unit_name, + from_date=actions.period.start.strftime("%d/%m/%Y %H:%M UTC"), + to_date=actions.period.end.strftime("%d/%m/%Y %H:%M UTC"), + ) + + html = css_inline.inline(html) + return html + + +@task(checkpoint=False) +def create_email( + html: str, actions: ControlUnitActions, test_mode: bool +) -> EmailMessage: + + buf = BytesIO() + actions.env_actions.to_csv(buf, mode="wb", index=False) + buf.seek(0) + + to = ( + CACEM_EMAIL_ADDRESS + if test_mode + else actions.control_unit.email_addresses + ) + + message = create_html_email( + to=to, + subject="Bilan hebdomadaire contrôle de l'environnement marin", + html=html, + attachments={"env_actions.csv": buf.read()}, + reply_to=CACEM_EMAIL_ADDRESS, + ) + + return message + + +@task(checkpoint=False) +def send_env_actions_email( + message: EmailMessage, actions: ControlUnitActions, is_integration: bool +) -> List[ControlUnitActionsSentMessage]: + """ + Sends input email using the contents of `From` header as sender and `To`, `Cc` + and `Bcc` headers as recipients. + + Args: + message (EmailMessage): email message to send + actions (ControlUnitActions): `ControlUnitActions` related to message + is_integration (bool): if ``False``, the message is not actually sent + + Returns: + List[ControlUnitActionsSentMessage]: List of sent messages and their error + codes, if any. + """ + + logger = prefect.context.get("logger") + addressees = actions.control_unit.email_addresses + + try: + try: + if is_integration: + logger.info(("(Mock) Sending env actions.")) + send_errors = {} + else: + logger.info(("Sending env actions.")) + send_errors = send_email(message) + except (SMTPHeloError, SMTPDataError): + # Retry + logger.warning("Message not sent, retrying...") + sleep(10) + send_errors = send_email(message) + except SMTPHeloError: + send_errors = { + addr: ( + None, + "The server didn't reply properly to the helo greeting.", + ) + for addr in addressees + } + logger.error(str(send_errors)) + except SMTPRecipientsRefused: + # All recipients were refused + send_errors = { + addr: ( + None, + "The server rejected ALL recipients (no mail was sent)", + ) + for addr in addressees + } + logger.error(str(send_errors)) + except SMTPSenderRefused: + send_errors = { + addr: ( + None, + "The server didn't accept the from_addr.", + ) + for addr in addressees + } + logger.error(str(send_errors)) + except SMTPDataError: + send_errors = { + addr: ( + None, + ( + "The server replied with an unexpected error code " + "(other than a refusal of a recipient)." + ), + ) + for addr in addressees + } + logger.error(str(send_errors)) + except SMTPNotSupportedError: + send_errors = { + addr: ( + None, + ( + "The mail_options parameter includes 'SMTPUTF8' but the SMTPUTF8 " + "extension is not supported by the server." + ), + ) + for addr in addressees + } + logger.error(str(send_errors)) + except ValueError: + send_errors = { + addr: ( + None, + "there is more than one set of 'Resent-' headers.", + ) + for addr in addressees + } + logger.error(str(send_errors)) + except Exception: + send_errors = {addr: (None, "Unknown error.") for addr in addressees} + logger.error(str(send_errors)) + + now = datetime.utcnow() + + sent_messages = [] + + for addressee in addressees: + if addressee in send_errors: + success = False + error_code = send_errors[addressee][0] + error_message = send_errors[addressee][1] + else: + success = True + error_code = None + error_message = None + + sent_messages.append( + ControlUnitActionsSentMessage( + control_unit_id=actions.control_unit.control_unit_id, + control_unit_name=actions.control_unit.control_unit_name, + email_address=addressee, + sending_datetime_utc=now, + actions_min_datetime_utc=actions.period.start, + actions_max_datetime_utc=actions.period.end, + number_of_actions=len(actions.env_actions), + success=success, + error_code=error_code, + error_message=error_message, + ) + ) + return sent_messages + + +@task(checkpoint=False) +def control_unit_actions_list_to_df( + messages: List[ControlUnitActionsSentMessage], +) -> pd.DataFrame: + messages = pd.DataFrame(messages) + return messages + + +@task(checkpoint=False) +def load_emails_sent_to_control_units( + emails_sent_to_control_units: pd.DataFrame, +): + load( + emails_sent_to_control_units, + table_name="emails_sent_to_control_units", + schema="public", + db_name="monitorenv_remote", + how="replace", + nullable_integer_columns=["error_code"], + logger=prefect.context.get("logger"), + ) + + +with Flow("Email actions to units", executor=LocalDaskExecutor()) as flow: + flow_not_running = check_flow_not_running() + with case(flow_not_running, True): + test_mode = Parameter("test_mode") + is_integration = Parameter("is_integration") + start_days_ago = Parameter("start_days_ago") + end_days_ago = Parameter("end_days_ago") + + template = get_template() + utcnow = get_utcnow() + + period = get_actions_period( + utcnow=utcnow, + start_days_ago=start_days_ago, + end_days_ago=end_days_ago, + ) + env_actions = extract_env_actions(period=period) + control_unit_ids = get_control_unit_ids(env_actions) + control_units = extract_control_units(control_unit_ids) + + control_unit_actions = to_control_unit_actions( + env_actions, period, control_units + ) + + html = render.map(control_unit_actions, template=unmapped(template)) + + message = create_email.map( + html=html, + actions=control_unit_actions, + test_mode=unmapped(test_mode), + ) + message = filter_results(message) + + sent_messages = send_env_actions_email.map( + message, + control_unit_actions, + is_integration=unmapped(is_integration), + ) + + sent_messages = flatten(sent_messages) + sent_messages = control_unit_actions_list_to_df(sent_messages) + load_emails_sent_to_control_units(sent_messages) + +flow.file_name = Path(__file__).name diff --git a/datascience/src/pipeline/flows_config.py b/datascience/src/pipeline/flows_config.py index 923a9cf6d..4a6775ad9 100644 --- a/datascience/src/pipeline/flows_config.py +++ b/datascience/src/pipeline/flows_config.py @@ -8,13 +8,16 @@ from config import ( DOCKER_IMAGE, FLOWS_LOCATION, + IS_INTEGRATION, MONITORENV_VERSION, ROOT_DIRECTORY, + TEST_MODE, ) from src.pipeline.flows import ( admin_areas, amp, control_objectives, + email_actions_to_units, facade_areas, facade_areas_unextended, fao_areas, @@ -30,7 +33,19 @@ ################################ Define flow schedules ################################ amp.flow.schedule = CronSchedule("22 0 * * *") - +email_actions_to_units.flow.schedule = Schedule( + clocks=[ + clocks.CronClock( + "* * * * *", + parameter_defaults={ + "start_days_ago": 7, + "end_days_ago": 1, + "test_mode": TEST_MODE, + "is_integration": IS_INTEGRATION, + }, + ), + ] +) infractions.flow.schedule = CronSchedule("2 8,14 * * *") refresh_materialized_view.flow.schedule = Schedule( @@ -59,6 +74,7 @@ admin_areas.flow, amp.flow, control_objectives.flow, + email_actions_to_units.flow, facade_areas.flow, facade_areas_unextended.flow, fao_areas.flow, diff --git a/datascience/src/pipeline/helpers/emails.py b/datascience/src/pipeline/helpers/emails.py new file mode 100644 index 000000000..ed5461eab --- /dev/null +++ b/datascience/src/pipeline/helpers/emails.py @@ -0,0 +1,152 @@ +import mimetypes +import smtplib +from email.message import EmailMessage +from mimetypes import guess_type +from pathlib import Path +from typing import List, Union + +from config import ( + EMAIL_SERVER_PORT, + EMAIL_SERVER_URL, + MONITORENV_SENDER_EMAIL_ADDRESS, +) + + +def create_html_email( + to: Union[str, List[str]], + subject: str, + html: str, + from_: str = MONITORENV_SENDER_EMAIL_ADDRESS, + cc: Union[str, List[str]] = None, + bcc: Union[str, List[str]] = None, + images: List[Path] = None, + attachments: dict = None, + reply_to: str = None, +) -> EmailMessage: + """ + Creates a `email.EmailMessage` with the defined parameters. + + Args: + to (Union[str, List[str]]): email address or list of email addresses of + recipient(s) + subject (str): Subject of the email. + html (str): html representation of the email's content. + from_ (str, optional): `From` field. Defaults to env var + `MONITORENV_SENDER_EMAIL_ADDRESS`. + cc (Union[str, List[str]], optional): `Cc` field with optional email address + (or list of email addresses) of copied recipient(s). Defaults to None. + bcc (Union[str, List[str]], optional): `Bcc` field with optional email address + (or list of email addresses) of hidden copied recipient(s). Defaults to None. + images (List[Path], optional): List of `Path` to images on the server's file + system to attach to the email. These images can be displayed in the html body + of the email by referencing them in the `src` attribute of an `` tag as + `cid:`, where `` is the image file's name. + + For example: `/a/b/c/my_image_123.png` can be included in the html message + as : + + `` in the html message. + + Defaults to None. + attachments (dict, optional): `dict` of attachments to add to the email. + Consists of {filename : bytes} value pairs. Defaults + to None. + reply_to (str, optional): if given, added as `Reply-To` header. Defaults to + None. + + Returns: + EmailMessage + """ + + if isinstance(to, list): + to = ", ".join(to) + + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = from_ + msg["To"] = to + + if cc: + if isinstance(cc, list): + cc = ", ".join(cc) + msg["Cc"] = cc + + if bcc: + if isinstance(bcc, list): + bcc = ", ".join(bcc) + msg["Bcc"] = bcc + + if reply_to: + msg["Reply-To"] = reply_to + + msg.set_content(html, subtype="html") + + if images: + for image in images: + (mimetype, _) = guess_type(image) + (maintype, subtype) = mimetype.split("/") + + with open(image, "rb") as f: + img_data = f.read() + msg.add_related( + img_data, + maintype=maintype, + subtype=subtype, + filename=image.name, + cid=f"<{image.name}>", + ) + + if attachments: + for filename, content in attachments.items(): + ctype, encoding = mimetypes.guess_type(filename) + if ctype is None or encoding is not None: + # No guess could be made, or the file is encoded (compressed), so + # use a generic bag-of-bits type. + ctype = "application/octet-stream" + maintype, subtype = ctype.split("/", 1) + msg.add_attachment( + content, + maintype=maintype, + subtype=subtype, + filename=filename, + ) + + return msg + + +def send_email(msg: EmailMessage) -> dict: + """ + Sends input email using the contents of `From` header as sender and `To`, `Cc` + and `Bcc` headers as recipients. + + This method will return normally if the mail is accepted for at least + one recipient. It returns a dictionary, with one entry for each + recipient that was refused. Each entry contains a tuple of the SMTP + error code and the accompanying error message sent by the server, like : + + { "three@three.org" : ( 550 ,"User unknown" ) } + + Args: + msg (EmailMessage): `email.message.EmailMessage` to send. + + Returns: + dict: {email_address : (error_code, error_message)} for all recipients that + were refused. + + Raises: + SMTPHeloError: The server didn't reply properly to the helo greeting. + SMTPRecipientsRefused: The server rejected ALL recipients (no mail was sent). + SMTPSenderRefused: The server didn't accept the from_addr. + SMTPDataError: The server replied with an unexpected error code (other than a + refusal of a recipient). + SMTPNotSupportedError: The mail_options parameter includes 'SMTPUTF8' but the + SMTPUTF8 extension is not supported by the server. + ValueError: if there is more than one set of 'Resent-' headers + """ + + assert EMAIL_SERVER_URL is not None + assert EMAIL_SERVER_PORT is not None + + with smtplib.SMTP(host=EMAIL_SERVER_URL, port=EMAIL_SERVER_PORT) as server: + send_errors = server.send_message(msg) + return send_errors diff --git a/datascience/src/pipeline/queries/monitorenv/control_units.sql b/datascience/src/pipeline/queries/monitorenv/control_units.sql new file mode 100644 index 000000000..ec23e2076 --- /dev/null +++ b/datascience/src/pipeline/queries/monitorenv/control_units.sql @@ -0,0 +1,13 @@ +SELECT + cu.id AS control_unit_id, + cu.name AS control_unit_name, + ARRAY_AGG(DISTINCT email) AS email_addresses +FROM control_units cu +JOIN control_unit_contacts cuc +ON cu.id = cuc.control_unit_id +WHERE + cu.id IN :control_unit_ids + AND email IS NOT NULL + AND is_email_subscription_contact +GROUP BY 1, 2 +ORDER BY 1, 2 diff --git a/datascience/src/pipeline/queries/monitorenv/env_actions_to_email.sql b/datascience/src/pipeline/queries/monitorenv/env_actions_to_email.sql new file mode 100644 index 000000000..c29bc0b70 --- /dev/null +++ b/datascience/src/pipeline/queries/monitorenv/env_actions_to_email.sql @@ -0,0 +1,62 @@ +WITH actions_to_export AS ( + SELECT DISTINCT ON (id, control_unit_id) + id, + mission_id, + action_start_datetime_utc, + action_end_datetime_utc, + mission_start_datetime_utc, + mission_end_datetime_utc, + mission_type, + action_type, + mission_facade, + control_unit_id, + control_unit, + administration, + action_facade, + action_department, + longitude, + latitude, + infraction, + number_of_controls, + surveillance_duration, + observations_cacem + FROM analytics_actions + WHERE ( + action_type = 'SURVEILLANCE' + AND action_end_datetime_utc >= :min_datetime_utc + AND action_end_datetime_utc < :max_datetime_utc + ) OR ( + action_type = 'CONTROL' + AND action_start_datetime_utc >= :min_datetime_utc + AND action_start_datetime_utc < :max_datetime_utc + ) +), + +actions_unique_themes_and_subthemes AS ( + SELECT DISTINCT + id, + theme_level_1, + theme_level_2 + FROM analytics_actions + WHERE id IN (SELECT id FROM actions_to_export) +), + +action_themes AS ( + SELECT DISTINCT + id, + jsonb_build_object(theme_level_1, jsonb_agg(theme_level_2) OVER (PARTITION BY id, theme_level_1)) AS theme + FROM actions_unique_themes_and_subthemes +), + +action_themes_and_subthemes AS ( + SELECT + id, + jsonb_agg(theme) AS themes + FROM action_themes + GROUP BY id +) + +SELECT a.*, t.themes +FROM actions_to_export a +LEFT JOIN action_themes_and_subthemes t +ON a.id = t.id \ No newline at end of file diff --git a/datascience/src/pipeline/shared_tasks/control_flow.py b/datascience/src/pipeline/shared_tasks/control_flow.py index 083251603..6e59b47ec 100644 --- a/datascience/src/pipeline/shared_tasks/control_flow.py +++ b/datascience/src/pipeline/shared_tasks/control_flow.py @@ -1,7 +1,9 @@ from pathlib import Path +from typing import List import prefect from prefect import task +from prefect.engine.signals import SKIP from prefect.utilities.graphql import with_args @@ -56,3 +58,52 @@ def str_to_path(path: str) -> Path: Path: Path('stairway/to/heaven') """ return Path(path) + + +@task( + checkpoint=False, + skip_on_upstream_skip=False, + trigger=prefect.triggers.all_finished, +) +def filter_results(task_results) -> List: + """ + Filters invalid results from an input mapped results list. + + Warning: It is mandatory to use `skip_on_upstream_skip = False`, otherwise the whole + task is skipped if any of the results of the upstream mapped task raises a SKIP + signal, which defeats the purpose of this task which is to discard the invalid + results produced by the upstream tasks and keep the valid ones and pass them on + downstream. + + As a consequence, + + !!!! THIS TASK WILL RUN EVEN IF THE UPSTREAM TASKS ARE SKIPPED !!!! + + This can have weird consequences, as the task can run successfully, and pass on + successful results to downstream tasks which were also supposed to be skipped. + + This happens including in a `case` branch of a flow that should be skipped. + + In order to try to circumvent this issue, we raise a `SKIP` signal if the input is + `None`, which is what happens if the input is provided by an upsteam task that is + itself skipped. This does not cover all cases at all, in particular, in situations + where tasks may be skipped (in case branch mostly) make sure not to use this task + with input data coming from upstream tasks that may not be skipped. + + Args: + task_results: List of (mapped) task results. + + Raises: + SKIP: If input is `None` + + Returns: + List: Filtered list with SKIPs, Nones and Errors removed + """ + if isinstance(task_results, list): + return [ + r + for r in task_results + if not isinstance(r, (BaseException, SKIP, type(None))) + ] + elif task_results is None: + raise SKIP diff --git a/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql b/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql index 3f4e720d9..1054c4c3e 100644 --- a/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql +++ b/datascience/tests/test_data/remote_database/V666.1__Reset_test_controllers.sql @@ -54,6 +54,14 @@ INSERT INTO public.control_unit_resources( ( 8, 1, 10019, 'PAM Jeanne Barret', 'PATROL_BOAT'), ( 10, 1, 10018, 'PAM Themis', 'PATROL_BOAT'); +INSERT INTO public.control_unit_contacts ( + control_unit_id, email, name, phone, created_at_utc, updated_at_utc, is_email_subscription_contact, is_sms_subscription_contact) VALUES + ( 10018, 'p602@ca.plane.pour.moi', 'UNIT_CHIEF', 'Balance ton 06', '2020-01-05 12:36:52Z', '2022-01-05 10:36:22Z', false, true), + ( 10018, 'diffusion.p602@email.fr', 'OFFICE', NULL, '2020-02-08 00:36:42Z', '2022-01-05 10:37:22Z', true, true), + ( 10018, 'diffusion_bis.p602@email.fr', 'OFFICE', NULL, '2020-02-08 00:36:42Z', '2022-01-05 10:37:22Z', true, true), + ( 10019, 'bn_toulon@email.fr', 'OFFICE', 'Le 07 du chef', '2020-03-21 10:35:20Z', '2022-01-05 10:38:22Z', true, true), + ( 10002, 'dml59@surveillance.fr', 'Jean-Mich du 59', '0000000000', '2020-01-05 09:35:39Z', '2022-01-05 10:39:22Z', false, true); + -- -- Add historic control units -- diff --git a/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql b/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql index 54e83bffa..0ec0f7955 100644 --- a/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql +++ b/datascience/tests/test_data/remote_database/V666.3__insert_dummy_env_actions.sql @@ -5,10 +5,10 @@ INSERT INTO public.env_actions ( id, mission_id, action_type, completion, value, action_start_datetime_utc, action_end_datetime_utc, geom) VALUES - ('dfb9710a-2217-4f98-94dc-283d3b7bbaae', 12, 'SURVEILLANCE', 'TO_COMPLETE', '{}', '2022-11-20 20:31:41.719', '2022-11-20 23:31:41.719', ST_GeomFromText('MULTIPOLYGON(((-1.22 46.27,-1.22 46.27,-1.20 46.26,-1.19 46.26,-1.22 46.27)))', 4326)), + ('dfb9710a-2217-4f98-94dc-283d3b7bbaae', 12, 'SURVEILLANCE', 'COMPLETED', '{}', '2022-11-20 20:31:41.719', '2022-11-20 23:31:41.719', ST_GeomFromText('MULTIPOLYGON(((-1.22 46.27,-1.22 46.27,-1.20 46.26,-1.19 46.26,-1.22 46.27)))', 4326)), ('d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2', 12, 'CONTROL', 'COMPLETED', '{"infractions": [], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 0, "actionStartDateTimeUtc": null}', '2022-11-24 20:31:41.719', NULL, ST_GeomFromText('MULTIPOINT(-2.9822 48.1236,-3.0564 48.1177)', 4326)), - ('88713755-3966-4ca4-ae18-10cab6249485', 19, 'SURVEILLANCE', 'TO_COMPLETE', '{"observations": "Surveillance ok", "protectedSpecies": []}', '2022-11-28 13:59:20.176', '2022-12-5 19:59:20.176', ST_GeomFromText('MULTIPOLYGON(((25 40,26 40,26 41,25 41,25 40)))', 4326)), - ('b05d96b8-387f-4599-bff0-cd7dab71dfb8', 20, 'CONTROL', 'COMPLETED', '{"infractions": [{"id": "c52c6f20-e495-4b29-b3df-d7edfb67fdd7", "natinf": ["10038", "10054"], "toProcess": false, "vesselSize": "FROM_24_TO_46m", "vesselType": "COMMERCIAL", "companyName": null, "formalNotice": "PENDING", "observations": "Pas d''observations", "relevantCourt": "LOCAL_COURT", "infractionType": "WITH_REPORT", "registrationNumber": "BALTIK", "controlledPersonIdentity": "John Doe"}], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 1, "actionStartDateTimeUtc": null}', '2022-11-17 13:59:51.108', NULL, ST_GeomFromText('MULTIPOINT(-2.52 47.16)', 4326)), + ('88713755-3966-4ca4-ae18-10cab6249485', 19, 'SURVEILLANCE', 'COMPLETED', '{"observations": "Surveillance ok", "protectedSpecies": []}', '2022-11-28 13:59:20.176', '2022-12-5 19:59:20.176', ST_GeomFromText('MULTIPOLYGON(((25 40,26 40,26 41,25 41,25 40)))', 4326)), + ('b05d96b8-387f-4599-bff0-cd7dab71dfb8', 20, 'CONTROL', 'TO_COMPLETE', '{"infractions": [{"id": "c52c6f20-e495-4b29-b3df-d7edfb67fdd7", "natinf": ["10038", "10054"], "toProcess": false, "vesselSize": "FROM_24_TO_46m", "vesselType": "COMMERCIAL", "companyName": null, "formalNotice": "PENDING", "observations": "Pas d''observations", "relevantCourt": "LOCAL_COURT", "infractionType": "WITH_REPORT", "registrationNumber": "BALTIK", "controlledPersonIdentity": "John Doe"}], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 1, "actionStartDateTimeUtc": null}', '2022-11-17 13:59:51.108', NULL, ST_GeomFromText('MULTIPOINT(-2.52 47.16)', 4326)), ('dedbd2c2-10f5-4d75-8fe9-c50db2ae5d0b', 20, 'CONTROL', 'TO_COMPLETE', '{"infractions": [], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "protectedSpecies": [], "actionNumberOfControls": 0, "actionStartDateTimeUtc": null}', '2022-11-24 20:31:41.719', NULL, NULL); INSERT INTO env_actions_control_plan_themes ( diff --git a/datascience/tests/test_data/remote_database/V666.6__Reset_dummy_emails_sent_to_control_units.sql b/datascience/tests/test_data/remote_database/V666.6__Reset_dummy_emails_sent_to_control_units.sql new file mode 100644 index 000000000..0d7347a1d --- /dev/null +++ b/datascience/tests/test_data/remote_database/V666.6__Reset_dummy_emails_sent_to_control_units.sql @@ -0,0 +1,3 @@ +DELETE FROM public.emails_sent_to_control_units; + +ALTER SEQUENCE emails_sent_to_control_units_id_seq RESTART WITH 1; \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/expected_rendered_email.html b/datascience/tests/test_pipeline/test_flows/expected_rendered_email.html new file mode 100644 index 000000000..0dbd2d682 --- /dev/null +++ b/datascience/tests/test_pipeline/test_flows/expected_rendered_email.html @@ -0,0 +1,28 @@ + + Bilan hebdomadaire contrôle de l'environnement marin + + + + +
+

Bilan hebdomadaire contrôle de l'environnement marin

+
+
+
+
+

Bonjour,

+

Vous trouverez ci-joint les données des contrôles et des surveillances de l'environnement marin auquels + votre unité (Nom de l'unité) a participé entre le 23/06/2020 00:00 UTC et le 06/05/2020 18:45 UTC qui ont été prises en compte + par le Centre d'Appui au Contrôle de l'Environnement Marin (CACEM).

+

Seuls les contrôles et surveillances des missions clôturées sont transmis dans ce bilan hebdomadaire. Si votre unité est engagée + dans une mission qui court sur plusieurs jours et qui n'était pas clôturée au 06/05/2020 18:45 UTC, il est normal que les données + correspondantes n'y figurent pas.

+

Si des données sont manquantes, incorrectes ou incomplètes, n'hésitez pas à contacter le CACEM.

+
+
+
+
+

Centre d'Appui au Contrôle de l'Environnement Marin - Tél : +33 2 90 74 32 55

+
+ + \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_email_actions_to_units.py b/datascience/tests/test_pipeline/test_flows/test_email_actions_to_units.py new file mode 100644 index 000000000..c093dc97d --- /dev/null +++ b/datascience/tests/test_pipeline/test_flows/test_email_actions_to_units.py @@ -0,0 +1,668 @@ +from datetime import datetime +from email.message import EmailMessage +from pathlib import Path +from smtplib import SMTPDataError +from typing import List +from unittest.mock import patch +from uuid import UUID + +import pandas as pd +import pytest +from jinja2 import Template + +from config import CACEM_EMAIL_ADDRESS, MONITORENV_SENDER_EMAIL_ADDRESS +from src.pipeline.entities.actions_emailing import ( + ControlUnit, + ControlUnitActions, + ControlUnitActionsSentMessage, +) +from src.pipeline.flows.email_actions_to_units import ( + control_unit_actions_list_to_df, + create_email, + extract_control_units, + extract_env_actions, + flow, + get_actions_period, + get_control_unit_ids, + get_template, + load_emails_sent_to_control_units, + render, + send_env_actions_email, + to_control_unit_actions, +) +from src.pipeline.helpers.dates import Period +from src.read_query import read_query +from tests.mocks import mock_check_flow_not_running + +flow.replace( + flow.get_tasks("check_flow_not_running")[0], mock_check_flow_not_running +) + + +@pytest.fixture +def expected_env_actions() -> pd.DataFrame: + C1 = ( + "Difficult ahead let really old around. " + "Cover operation seven surface use show. " + "Manage beautiful reason account prepare evening sure." + ) + + C2 = ( + "Mother including baby same. " + "Evidence project air practice minute their. " + "Trouble sing suggest maintain like know too." + ) + + return pd.DataFrame( + { + "id": [ + UUID("88713755-3966-4ca4-ae18-10cab6249485"), + UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), + UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), + UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), + UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), + ], + "mission_id": [19, 12, 12, 12, 12], + "action_start_datetime_utc": [ + datetime( + year=2022, + month=11, + day=28, + hour=13, + minute=59, + second=20, + microsecond=176000, + ), + datetime( + year=2022, + month=11, + day=24, + hour=20, + minute=31, + second=41, + microsecond=719000, + ), + datetime( + year=2022, + month=11, + day=24, + hour=20, + minute=31, + second=41, + microsecond=719000, + ), + datetime( + year=2022, + month=11, + day=20, + hour=20, + minute=31, + second=41, + microsecond=719000, + ), + datetime( + year=2022, + month=11, + day=20, + hour=20, + minute=31, + second=41, + microsecond=719000, + ), + ], + "action_end_datetime_utc": [ + datetime( + year=2022, + month=12, + day=5, + hour=19, + minute=59, + second=20, + microsecond=176000, + ), + pd.NaT, + pd.NaT, + datetime( + year=2022, + month=11, + day=20, + hour=23, + minute=31, + second=41, + microsecond=719000, + ), + datetime( + year=2022, + month=11, + day=20, + hour=23, + minute=31, + second=41, + microsecond=719000, + ), + ], + "mission_start_datetime_utc": [ + datetime( + year=2022, month=6, day=21, hour=13, minute=24, second=4 + ), + datetime( + year=2022, month=2, day=24, hour=10, minute=56, second=33 + ), + datetime( + year=2022, month=2, day=24, hour=10, minute=56, second=33 + ), + datetime( + year=2022, month=2, day=24, hour=10, minute=56, second=33 + ), + datetime( + year=2022, month=2, day=24, hour=10, minute=56, second=33 + ), + ], + "mission_end_datetime_utc": [ + datetime( + year=2022, month=7, day=18, hour=2, minute=49, second=8 + ), + datetime( + year=2022, month=5, day=6, hour=19, minute=38, second=29 + ), + datetime( + year=2022, month=5, day=6, hour=19, minute=38, second=29 + ), + datetime( + year=2022, month=5, day=6, hour=19, minute=38, second=29 + ), + datetime( + year=2022, month=5, day=6, hour=19, minute=38, second=29 + ), + ], + "mission_type": ["SEA", "SEA", "SEA", "SEA", "SEA"], + "action_type": [ + "SURVEILLANCE", + "CONTROL", + "CONTROL", + "SURVEILLANCE", + "SURVEILLANCE", + ], + "mission_facade": ["NAMO", "NAMO", "NAMO", "NAMO", "NAMO"], + "control_unit_id": [10019, 10018, 10019, 10018, 10019], + "control_unit": [ + "BN Toulon", + "P602 Verdon", + "BN Toulon", + "P602 Verdon", + "BN Toulon", + ], + "administration": [ + "Gendarmerie Nationale", + "Gendarmerie Maritime", + "Gendarmerie Nationale", + "Gendarmerie Maritime", + "Gendarmerie Nationale", + ], + "action_facade": [ + "Hors façade", + "Hors façade", + "Hors façade", + "Hors façade", + "Hors façade", + ], + "action_department": [ + "Hors département", + "Hors département", + "Hors département", + "Hors département", + "Hors département", + ], + "longitude": [None, -3.0564, -2.9822, None, None], + "latitude": [None, 48.1177, 48.1236, None, None], + "infraction": [None, False, False, None, None], + "number_of_controls": [None, 0.0, 0.0, None, None], + "surveillance_duration": [174.0, None, None, 3.0, 3.0], + "observations_cacem": [C1, C2, C2, C2, C2], + "themes": [ + [{"Culture marine": ["Implantation"]}], + [{"Aucun thème": ["Aucun sous-thème"]}], + [{"Aucun thème": ["Aucun sous-thème"]}], + [ + { + "Activités et manifestations soumises à évaluation d’incidence Natura 2000": [ + "Aucun sous-thème" + ] + } + ], + [ + { + "Activités et manifestations soumises à évaluation d’incidence Natura 2000": [ + "Aucun sous-thème" + ] + } + ], + ], + } + ) + + +@pytest.fixture +def expected_control_unit_ids() -> List[int]: + return [10018, 10019] + + +@pytest.fixture +def expected_control_units() -> pd.DataFrame: + return pd.DataFrame( + { + "control_unit_id": [10018, 10019], + "control_unit_name": ["P602 Verdon", "BN Toulon"], + "email_addresses": [ + ["diffusion.p602@email.fr", "diffusion_bis.p602@email.fr"], + ["bn_toulon@email.fr"], + ], + } + ) + + +@pytest.fixture +def sample_control_unit_actions() -> ControlUnitActions: + return ControlUnitActions( + control_unit=ControlUnit( + control_unit_id=13, + control_unit_name="Nom de l'unité", + email_addresses=["email@email.com", "email2@email.com"], + ), + period=Period( + start=datetime(2020, 6, 23, 0, 0, 0), + end=datetime(2020, 5, 6, 18, 45, 6), + ), + env_actions=pd.DataFrame({"some_column": ["some", "data"]}), + ) + + +@pytest.fixture +def control_unit_actions_sent_messages() -> List[ + ControlUnitActionsSentMessage +]: + + return [ + ControlUnitActionsSentMessage( + control_unit_id=13, + control_unit_name="Nom de l'unité", + email_address="email@email.com", + sending_datetime_utc=datetime(2024, 3, 19, 14, 37, 24, 497093), + actions_min_datetime_utc=datetime(2020, 6, 23, 0, 0), + actions_max_datetime_utc=datetime(2020, 5, 6, 18, 45, 6), + number_of_actions=2, + success=True, + error_code=None, + error_message=None, + ), + ControlUnitActionsSentMessage( + control_unit_id=13, + control_unit_name="Nom de l'unité", + email_address="email2@email.com", + sending_datetime_utc=datetime(2024, 3, 19, 14, 37, 24, 497093), + actions_min_datetime_utc=datetime(2020, 6, 23, 0, 0), + actions_max_datetime_utc=datetime(2020, 5, 6, 18, 45, 6), + number_of_actions=2, + success=False, + error_code=550, + error_message="Email cound not be sent.", + ), + ] + + +@pytest.fixture +def control_unit_actions_sent_messages_df() -> pd.DataFrame: + return pd.DataFrame( + { + "control_unit_id": [13, 13], + "control_unit_name": ["Nom de l'unité", "Nom de l'unité"], + "email_address": ["email@email.com", "email2@email.com"], + "sending_datetime_utc": [ + datetime( + year=2024, + month=3, + day=19, + hour=14, + minute=37, + second=24, + microsecond=497093, + ), + datetime( + year=2024, + month=3, + day=19, + hour=14, + minute=37, + second=24, + microsecond=497093, + ), + ], + "actions_min_datetime_utc": [ + datetime( + year=2020, month=6, day=23, hour=00, minute=00, second=00 + ), + datetime( + year=2020, month=6, day=23, hour=00, minute=00, second=00 + ), + ], + "actions_max_datetime_utc": [ + datetime( + year=2020, month=5, day=6, hour=18, minute=45, second=6 + ), + datetime( + year=2020, month=5, day=6, hour=18, minute=45, second=6 + ), + ], + "number_of_actions": [2, 2], + "success": [True, False], + "error_code": [None, 550.0], + "error_message": [None, "Email cound not be sent."], + } + ) + + +def test_get_actions_period(): + period = get_actions_period.run( + utcnow=datetime(2021, 2, 21, 16, 10, 0), + start_days_ago=5, + end_days_ago=2, + ) + assert period == Period( + start=datetime(2021, 2, 16, 0, 0), end=datetime(2021, 2, 20, 0, 0) + ) + + +def test_extract_env_actions(reset_test_data, expected_env_actions): + actions = extract_env_actions.run( + period=Period(start=datetime(2022, 10, 1), end=datetime(2022, 12, 6)) + ) + assert len(actions) == 5 + assert set(actions.id.unique()) == set( + { + UUID("dfb9710a-2217-4f98-94dc-283d3b7bbaae"), + UUID("88713755-3966-4ca4-ae18-10cab6249485"), + UUID("d8e580fe-8e71-4303-a0c3-a76e1d4e4fc2"), + } + ) + pd.testing.assert_frame_equal(actions, expected_env_actions) + + +def test_extract_env_actions_with_dates_without_controls( + reset_test_data, expected_env_actions +): + actions = extract_env_actions.run( + period=Period(start=datetime(2023, 10, 1), end=datetime(2023, 12, 6)) + ) + assert len(actions) == 0 + + +def test_get_control_unit_ids(expected_env_actions, expected_control_unit_ids): + ids = get_control_unit_ids.run(expected_env_actions) + assert ids == expected_control_unit_ids + id_types = set(map(type, ids)) + + # Type int required by psycopg2, which cannot handle numpy.int64 + assert id_types == {int} + + +def test_extract_control_units( + reset_test_data, expected_control_unit_ids, expected_control_units +): + units = extract_control_units.run( + control_unit_ids=expected_control_unit_ids + ) + units["email_addresses"] = units.email_addresses.map(sorted) + pd.testing.assert_frame_equal(units, expected_control_units) + + +def test_to_control_unit_actions(expected_env_actions, expected_control_units): + + period = Period( + start=datetime(1996, 6, 11, 2, 52, 36), + end=datetime(1996, 6, 13, 6, 17, 18), + ) + + control_unit_actions = to_control_unit_actions.run( + env_actions=expected_env_actions, + period=period, + control_units=expected_control_units, + ) + + assert len(control_unit_actions) == 2 + + assert isinstance(control_unit_actions[0], ControlUnitActions) + assert control_unit_actions[0].control_unit.control_unit_id == 10018 + assert control_unit_actions[0].period == period + pd.testing.assert_frame_equal( + control_unit_actions[0].env_actions, + expected_env_actions.iloc[[1, 3]].reset_index(drop=True), + ) + + assert control_unit_actions[1].control_unit.control_unit_id == 10019 + assert control_unit_actions[1].period == period + pd.testing.assert_frame_equal( + control_unit_actions[1].env_actions, + expected_env_actions.iloc[[0, 2, 4]].reset_index(drop=True), + ) + + +def test_get_template(): + template = get_template.run() + assert isinstance(template, Template) + + +def test_render(sample_control_unit_actions): + template = get_template.run() + html = render.run(actions=sample_control_unit_actions, template=template) + + # Uncomment to update the expected html file + # with open(Path(__file__).parent / "expected_rendered_email.html", "w") as f: + # f.write(html) + + with open( + Path(__file__).parent / "expected_rendered_email.html", "r" + ) as f: + expected_html = f.read() + + assert html == expected_html + + +@pytest.fixture +def expected_email(sample_control_unit_actions) -> EmailMessage: + + email = EmailMessage() + email["Subject"] = "Bilan hebdomadaire contrôle de l'environnement marin" + email["From"] = MONITORENV_SENDER_EMAIL_ADDRESS + email["To"] = ", ".join( + sample_control_unit_actions.control_unit.email_addresses + ) + email["Reply-To"] = CACEM_EMAIL_ADDRESS + email.set_content( + "Bonjour ceci est un email test.\n", subtype="html" + ) + + email.add_attachment( + b"some_column\nsome\ndata\n", + maintype="text", + subtype="csv", + filename="env_actions.csv", + ) + return email + + +@pytest.mark.parametrize("test_mode", [False, True]) +def test_create_email(sample_control_unit_actions, expected_email, test_mode): + + email = create_email.run( + html="Bonjour ceci est un email test.", + actions=sample_control_unit_actions, + test_mode=test_mode, + ) + + assert email["Subject"] == expected_email["Subject"] + assert email["From"] == expected_email["From"] + assert ( + email["To"] == CACEM_EMAIL_ADDRESS + if test_mode + else expected_email["To"] + ) + assert email["Reply-To"] == expected_email["Reply-To"] + assert email.get_content_type() == expected_email.get_content_type() + + expected_attachments = list(expected_email.iter_attachments()) + attachments = list(email.iter_attachments()) + + assert len(attachments) == len(expected_attachments) == 1 + attachment = attachments[0] + expected_attachment = expected_attachments[0] + assert ( + attachment.get_content_disposition() + == expected_attachment.get_content_disposition() + ) + assert ( + attachment.get_content_type() == expected_attachment.get_content_type() + ) + assert attachment.get_filename() == expected_attachment.get_filename() + assert attachment.get_content() == expected_attachment.get_content() + + body = email.get_body() + expected_body = expected_email.get_body() + assert body.get_content_type() == expected_body.get_content_type() + + assert body.get_charsets() == expected_body.get_charsets() + assert body.get_content() == expected_body.get_content() + + +@pytest.mark.parametrize( + "is_integration,send_email_outcome", + [ + (False, SMTPDataError(100, "Erreur SMTP")), + (False, dict()), + (False, {"email2@email.com": (550, "Email cound not be sent.")}), + (True, Exception("Autre erreur")), + ], +) +@patch("src.pipeline.flows.email_actions_to_units.send_email") +@patch("src.pipeline.flows.email_actions_to_units.sleep") +def test_send_env_actions_email( + mock_sleep, + mock_send_email, + expected_email, + sample_control_unit_actions, + is_integration, + send_email_outcome, +): + def send_email_side_effect(message): + if isinstance(send_email_outcome, Exception): + raise send_email_outcome + else: + return send_email_outcome + + mock_send_email.side_effect = send_email_side_effect + + sent_messages = send_env_actions_email.run( + message=expected_email, + actions=sample_control_unit_actions, + is_integration=is_integration, + ) + assert len(sent_messages) == 2 + for msg in sent_messages: + success = True + error_code = None + error_message = None + addressee = msg.email_address + if not is_integration: + if isinstance(send_email_outcome, SMTPDataError): + success = False + error_message = ( + "The server replied with an unexpected error code " + "(other than a refusal of a recipient)." + ) + else: + if msg.email_address in send_email_outcome: + success = False + error_code, error_message = send_email_outcome[addressee] + assert isinstance(msg, ControlUnitActionsSentMessage) + assert ( + msg.control_unit_id + == sample_control_unit_actions.control_unit.control_unit_id + ) + assert ( + msg.control_unit_name + == sample_control_unit_actions.control_unit.control_unit_name + ) + assert msg.email_address == addressee + assert ( + msg.actions_min_datetime_utc + == sample_control_unit_actions.period.start + ) + assert ( + msg.actions_max_datetime_utc + == sample_control_unit_actions.period.end + ) + assert msg.number_of_actions == len( + sample_control_unit_actions.env_actions + ) + assert msg.success == success + assert msg.error_code == error_code + assert msg.error_message == error_message + + +def test_control_unit_actions_list_to_df( + control_unit_actions_sent_messages, control_unit_actions_sent_messages_df +): + + df = control_unit_actions_list_to_df.run( + control_unit_actions_sent_messages + ) + pd.testing.assert_frame_equal(df, control_unit_actions_sent_messages_df) + + +def test_load_emails_sent_to_control_units( + reset_test_data, control_unit_actions_sent_messages_df +): + + query = "SELECT * FROM emails_sent_to_control_units ORDER BY email_address" + + initial_emails = read_query(db="monitorenv_remote", query=query) + + load_emails_sent_to_control_units.run( + control_unit_actions_sent_messages_df + ) + emails_after_one_run = read_query(db="monitorenv_remote", query=query) + + load_emails_sent_to_control_units.run( + control_unit_actions_sent_messages_df + ) + emails_after_two_runs = read_query(db="monitorenv_remote", query=query) + + assert len(initial_emails) == 0 + assert len(emails_after_one_run) == len(emails_after_two_runs) == 2 + pd.testing.assert_frame_equal( + emails_after_one_run.drop(columns=["id"]), + emails_after_two_runs.drop(columns=["id"]), + ) + + +def test_flow_run_blabla(reset_test_data): + now = datetime.utcnow() + d1 = datetime(2022, 10, 1) + d2 = datetime(2022, 12, 6) + start_days_ago = int((now - d1).total_seconds() / 60 / 60 / 24) + 1 + end_days_ago = int((now - d2).total_seconds() / 60 / 60 / 24) + 1 + + query = "SELECT * FROM emails_sent_to_control_units ORDER BY email_address" + initial_emails = read_query(db="monitorenv_remote", query=query) + + flow.schedule = None + state = flow.run( + test_mode=False, + is_integration=True, + start_days_ago=start_days_ago, + end_days_ago=end_days_ago, + ) + assert state.is_successful() + + final_emails = read_query(db="monitorenv_remote", query=query) + assert len(initial_emails) == 0 + assert len(final_emails) == 3 diff --git a/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py b/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py index 529e44382..80baf6052 100644 --- a/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py +++ b/datascience/tests/test_pipeline/test_flows/test_refresh_materialized_views.py @@ -57,8 +57,8 @@ def test_refresh_analytics_actions(reset_test_data): actions_after_refresh = read_query("monitorenv_remote", query) - assert len(initial_actions) == 5 - assert len(actions_before_refresh) == 5 + assert len(initial_actions) == 7 + assert len(actions_before_refresh) == 7 assert len(actions_after_refresh) == 1 pd.testing.assert_frame_equal(initial_actions, actions_before_refresh) diff --git a/frontend/cypress/e2e/main_window/control_unit_dialog/contact_list.spec.ts b/frontend/cypress/e2e/main_window/control_unit_dialog/contact_list.spec.ts index 83712292d..1f51ad553 100644 --- a/frontend/cypress/e2e/main_window/control_unit_dialog/contact_list.spec.ts +++ b/frontend/cypress/e2e/main_window/control_unit_dialog/contact_list.spec.ts @@ -1,16 +1,16 @@ import { goToMainWindowAndOpenControlUnit } from './utils' context('Main Window > Control Unit Dialog > Contact List', () => { - beforeEach(() => { + it('Should show all contacts by default', () => { goToMainWindowAndOpenControlUnit(10000) - }) - it('Should show all contacts by default', () => { cy.contains('Contact 1').should('be.visible') cy.contains('Contact 2').should('be.visible') }) it('Should validate the form', () => { + goToMainWindowAndOpenControlUnit(10000) + cy.clickButton('Ajouter un contact') cy.clickButton('Ajouter') @@ -24,75 +24,297 @@ context('Main Window > Control Unit Dialog > Contact List', () => { }) it('Should add, edit and delete a contact', () => { + goToMainWindowAndOpenControlUnit(10023) + // ------------------------------------------------------------------------- // Create - cy.intercept('POST', `/api/v1/control_unit_contacts`).as('createControlUnitContact') + cy.intercept('POST', `/api/v2/control_unit_contacts`).as('createControlUnitContact') cy.clickButton('Ajouter un contact') cy.fill('Nom du contact', 'Adjoint') cy.fill('Numéro de téléphone', '0123456789') + cy.clickButton('Ajouter ce numéro à la liste de diffusion des préavis et des bilans d’activités de contrôle') cy.fill('Adresse mail', 'foo@example.org') + cy.clickButton('Ajouter cette adresse à la liste de diffusion des préavis et des bilans d’activités de contrôle') cy.clickButton('Ajouter') - cy.wait('@createControlUnitContact').then(interception => { - if (!interception.response) { - assert.fail('`interception.response` is undefined.') + cy.wait('@createControlUnitContact').then(createInterception => { + if (!createInterception.response) { + assert.fail('`createInterception.response` is undefined.') } - assert.deepInclude(interception.request.body, { + assert.deepInclude(createInterception.request.body, { email: 'foo@example.org', + isEmailSubscriptionContact: true, + isSmsSubscriptionContact: true, name: 'ADJUNCT', phone: '0123456789' }) - }) - cy.get('p').contains('Ajouter un contact').should('not.exist') - cy.contains('Adjoint').should('be.visible') + const newControlUnitContactId = createInterception.response.body.id - // ------------------------------------------------------------------------- - // Edit + cy.get('p').contains('Ajouter un contact').should('not.exist') + cy.contains('Adjoint').should('be.visible') - cy.intercept('PUT', `/api/v1/control_unit_contacts/4`).as('updateControlUnitContact') + // ------------------------------------------------------------------------- + // Update - cy.getDataCy('ControlUnitDialog-control-unit-contact').filter('[data-id="4"]').clickButton('Éditer ce contact') + cy.intercept('PATCH', `/api/v1/control_unit_contacts/${newControlUnitContactId}`).as('patchControlUnitContact') - cy.fill('Nom du contact', 'Passerelle') - cy.fill('Numéro de téléphone', '9876543210') - cy.fill('Adresse mail', 'bar@example.org') + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${newControlUnitContactId}"]`) + .clickButton('Éditer ce contact') - cy.clickButton('Enregistrer les modifications') + cy.fill('Nom du contact', 'Passerelle') + cy.fill('Numéro de téléphone', '9876543210') + cy.fill('Adresse mail', 'bar@example.org') - cy.wait('@updateControlUnitContact').then(interception => { - if (!interception.response) { - assert.fail('`interception.response` is undefined.') - } + cy.clickButton('Enregistrer les modifications') + + cy.wait('@patchControlUnitContact').then(interception => { + if (!interception.response) { + assert.fail('`interception.response` is undefined.') + } - assert.deepInclude(interception.request.body, { - email: 'bar@example.org', - id: 4, - name: 'BRIDGE', - phone: '9876543210' + assert.deepInclude(interception.request.body, { + email: 'bar@example.org', + id: newControlUnitContactId, + isEmailSubscriptionContact: true, + isSmsSubscriptionContact: true, + name: 'BRIDGE', + phone: '9876543210' + }) }) + + cy.get('p').contains('Éditer un contact').should('not.exist') + cy.contains('Enregistrer les modifications').should('not.exist') + cy.contains('Passerelle').should('be.visible') + + // ------------------------------------------------------------------------- + // Delete + + cy.intercept('DELETE', `/api/v1/control_unit_contacts/${newControlUnitContactId}`).as('deleteControlUnitContact') + + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${newControlUnitContactId}"]`) + .clickButton('Éditer ce contact') + cy.clickButton('Supprimer ce contact') + cy.clickButton('Supprimer') + + // Warning banner + cy.get('.Component-Banner') + .should('be.visible') + .contains( + 'Cette unité n’a actuellement plus d’adresse de diffusion. Elle ne recevra plus de préavis ni de bilan de ses activités de contrôle.' + ) + + cy.wait('@deleteControlUnitContact') + + cy.contains('Passerelle').should('not.exist') }) + }) - cy.get('p').contains('Éditer un contact').should('not.exist') - cy.contains('Enregistrer les modifications').should('not.exist') - cy.contains('Passerelle').should('be.visible') + it('Should subscribe and unsubscribe contact to emails and sms', () => { + goToMainWindowAndOpenControlUnit(10023) - // ------------------------------------------------------------------------- - // Delete + cy.intercept('POST', `/api/v2/control_unit_contacts`).as('createControlUnitContact') + + cy.clickButton('Ajouter un contact') + cy.fill('Nom du contact', 'Adjoint') + cy.fill('Numéro de téléphone', '0111111111') + cy.fill('Adresse mail', 'first.contact@example.org') + cy.clickButton('Ajouter') + + cy.wait('@createControlUnitContact').then(firstCreateInterception => { + if (!firstCreateInterception.response) { + assert.fail('`firstCreateInterception.response` is undefined.') + } + + const firstControlUnitContactId = firstCreateInterception.response.body.id + + cy.clickButton('Ajouter un contact') + cy.fill('Nom du contact', 'Adjoint') + cy.fill('Numéro de téléphone', '0222222222') + cy.fill('Adresse mail', 'second.contact@example.org') + cy.clickButton('Ajouter') + + cy.wait('@createControlUnitContact').then(secondCreateInterception => { + if (!secondCreateInterception.response) { + assert.fail('`firstCreateInterception.response` is undefined.') + } + + const secondControlUnitContactId = secondCreateInterception.response.body.id + + cy.intercept('PATCH', `/api/v1/control_unit_contacts/${firstControlUnitContactId}`).as( + 'patchFirstControlUnitContact' + ) + cy.intercept('PATCH', `/api/v1/control_unit_contacts/${secondControlUnitContactId}`).as( + 'patchSecondControlUnitContact' + ) + + // Edit first contact + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${firstControlUnitContactId}"]`) + .clickButton('Éditer ce contact') + + // ------------------------------------------------------------------------- + // Subscribe phone (first contact) - cy.intercept('DELETE', `/api/v1/control_unit_contacts/4`).as('deleteControlUnitContact') + cy.clickButton('Ajouter ce numéro à la liste de diffusion des préavis et des bilans d’activités de contrôle') - cy.getDataCy('ControlUnitDialog-control-unit-contact').filter('[data-id="4"]').clickButton('Éditer ce contact') - cy.clickButton('Supprimer ce contact') - cy.clickButton('Supprimer') + // ------------------------------------------------------------------------- + // Subscribe email (first contact) - cy.wait('@deleteControlUnitContact') + cy.clickButton( + 'Ajouter cette adresse à la liste de diffusion des préavis et des bilans d’activités de contrôle' + ) - cy.contains('Passerelle').should('not.exist') + // Info message + cy.getDataCy('ControlUnitDialog-control-unit-contact-form') + .find('.Component-Message>') + .should('be.visible') + .contains('Adresse de diffusion') + + // Update first contact + cy.clickButton('Enregistrer les modifications') + + cy.wait('@patchFirstControlUnitContact').then(patchInterception => { + if (!patchInterception.response) { + assert.fail('`interception.response` is undefined.') + } + + assert.deepInclude(patchInterception.request.body, { + email: 'first.contact@example.org', + id: firstControlUnitContactId, + isEmailSubscriptionContact: true, + isSmsSubscriptionContact: true, + phone: '0111111111' + }) + }) + + // Edit second contact + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${secondControlUnitContactId}"]`) + .clickButton('Éditer ce contact') + + // ------------------------------------------------------------------------- + // Subscribe phone (second contact) + + cy.clickButton('Ajouter ce numéro à la liste de diffusion des préavis et des bilans d’activités de contrôle') + + // ------------------------------------------------------------------------- + // Subscribe another contact to email (second contact) + + cy.clickButton( + 'Ajouter cette adresse à la liste de diffusion des préavis et des bilans d’activités de contrôle' + ) + + // Warning confirmation message + cy.getDataCy('ControlUnitDialog-control-unit-contact-form') + .find('.Component-Message>') + .should('be.visible') + .contains('Attention') + .parent() + .contains('first.contact@example.org') + .parent() + .contains('second.contact@example.org') + + cy.clickButton('Oui, la remplacer') + + // Info message + cy.getDataCy('ControlUnitDialog-control-unit-contact-form') + .find('.Component-Message>') + .should('be.visible') + .contains('Adresse de diffusion') + + // Update second contact + cy.clickButton('Enregistrer les modifications') + + cy.wait('@patchSecondControlUnitContact').then(patchInterception => { + if (!patchInterception.response) { + assert.fail('`interception.response` is undefined.') + } + + assert.deepInclude(patchInterception.request.body, { + email: 'second.contact@example.org', + id: secondControlUnitContactId, + isEmailSubscriptionContact: true, + isSmsSubscriptionContact: true, + phone: '0222222222' + }) + }) + + // Edit second contact + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${secondControlUnitContactId}"]`) + .clickButton('Éditer ce contact') + + // ------------------------------------------------------------------------- + // Unsubscribe phone (second contact) + + cy.clickButton('Retirer ce numéro de la liste de diffusion des préavis et des bilans d’activités de contrôle') + + // ------------------------------------------------------------------------- + // Unsubscribe email (second contact) + + cy.clickButton( + 'Retirer cette adresse de la liste de diffusion des préavis et des bilans d’activités de contrôle' + ) + + // Update second contact + cy.clickButton('Enregistrer les modifications') + + // Warning banner + cy.get('.Component-Banner') + .should('be.visible') + .contains( + 'Cette unité n’a actuellement plus d’adresse de diffusion. Elle ne recevra plus de préavis ni de bilan de ses activités de contrôle.' + ) + + cy.wait('@patchSecondControlUnitContact').then(interception => { + if (!interception.response) { + assert.fail('`interception.response` is undefined.') + } + + assert.deepInclude(interception.request.body, { + email: 'second.contact@example.org', + id: secondControlUnitContactId, + isEmailSubscriptionContact: false, + isSmsSubscriptionContact: false, + phone: '0222222222' + }) + }) + + // ------------------------------------------------------------------------- + // Delete (reset) + + cy.intercept('DELETE', `/api/v1/control_unit_contacts/${firstControlUnitContactId}`).as( + 'deleteFirstControlUnitContact' + ) + + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${firstControlUnitContactId}"]`) + .clickButton('Éditer ce contact') + cy.clickButton('Supprimer ce contact') + cy.clickButton('Supprimer') + + cy.wait('@deleteFirstControlUnitContact') + + cy.intercept('DELETE', `/api/v1/control_unit_contacts/${secondControlUnitContactId}`).as( + 'deleteSecondControlUnitContact' + ) + + cy.getDataCy('ControlUnitDialog-control-unit-contact') + .filter(`[data-id="${secondControlUnitContactId}"]`) + .clickButton('Éditer ce contact') + cy.clickButton('Supprimer ce contact') + cy.clickButton('Supprimer') + + cy.wait('@deleteSecondControlUnitContact') + }) + }) }) }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 86c762649..7fb8cffa3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "AGPL-3.0", "dependencies": { - "@mtes-mct/monitor-ui": "14.3.1", + "@mtes-mct/monitor-ui": "17.0.0", "@reduxjs/toolkit": "1.9.7", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", @@ -1911,9 +1911,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3678,11 +3678,11 @@ "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==" }, "node_modules/@mtes-mct/monitor-ui": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-14.3.1.tgz", - "integrity": "sha512-B3/32REynPCvhfSaBSmiOAiHR7leNt57Ns62ooPQ3PrJRCn82LLYGrwo1UwazKHDjyrPpHzzpWqcP5EH+5DcJQ==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-17.0.0.tgz", + "integrity": "sha512-T8pBBI24bllsPqQoBdM76YxOFb6o/rovVryLBb2KCYdnZNKn1+mn+qEEGtj9vybcOnsCvlTDaFDGDHtsqImeRQ==", "dependencies": { - "@babel/runtime": "7.24.4", + "@babel/runtime": "7.24.5", "@tanstack/react-table": "8.9.7", "@tanstack/react-virtual": "beta", "prop-types": "15.8.1", @@ -9049,18 +9049,6 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/eslint-plugin-mocha": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1b7c75c38..48aa33f39 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "test:unit:watch": "npm run test:unit -- --watch" }, "dependencies": { - "@mtes-mct/monitor-ui": "14.3.1", + "@mtes-mct/monitor-ui": "17.0.0", "@reduxjs/toolkit": "1.9.7", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", diff --git a/frontend/src/api/controlUnitContactsAPI.ts b/frontend/src/api/controlUnitContactsAPI.ts index 00b2888de..093a9f3aa 100644 --- a/frontend/src/api/controlUnitContactsAPI.ts +++ b/frontend/src/api/controlUnitContactsAPI.ts @@ -1,8 +1,8 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' + import { monitorenvPublicApi } from './api' import { FrontendApiError } from '../libs/FrontendApiError' -import type { ControlUnit } from '../domain/entities/controlUnit' - const GET_CONTROL_UNIT_CONTACT_ERROR_MESSAGE = "Nous n'avons pas pu récupérer cette contact." const GET_CONTROL_UNIT_CONTACTS_ERROR_MESSAGE = "Nous n'avons pas pu récupérer la liste des contacts." @@ -13,7 +13,7 @@ export const controlUnitContactsAPI = monitorenvPublicApi.injectEndpoints({ query: newControlUnitContactData => ({ body: newControlUnitContactData, method: 'POST', - url: `/v1/control_unit_contacts` + url: `/v2/control_unit_contacts` }) }), @@ -37,11 +37,11 @@ export const controlUnitContactsAPI = monitorenvPublicApi.injectEndpoints({ transformErrorResponse: response => new FrontendApiError(GET_CONTROL_UNIT_CONTACTS_ERROR_MESSAGE, response) }), - updateControlUnitContact: builder.mutation({ + patchControlUnitContact: builder.mutation({ invalidatesTags: () => [{ type: 'ControlUnits' }], query: nextControlUnitContactData => ({ body: nextControlUnitContactData, - method: 'PUT', + method: 'PATCH', url: `/v1/control_unit_contacts/${nextControlUnitContactData.id}` }) }) @@ -53,5 +53,5 @@ export const { useDeleteControlUnitContactMutation, useGetControlUnitContactQuery, useGetControlUnitContactsQuery, - useUpdateControlUnitContactMutation + usePatchControlUnitContactMutation } = controlUnitContactsAPI diff --git a/frontend/src/api/controlUnitResourcesAPI.ts b/frontend/src/api/controlUnitResourcesAPI.ts index 5b01a0ad0..6cdeb453c 100644 --- a/frontend/src/api/controlUnitResourcesAPI.ts +++ b/frontend/src/api/controlUnitResourcesAPI.ts @@ -1,11 +1,11 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' + import { monitorenvPublicApi } from './api' import { ARCHIVE_GENERIC_ERROR_MESSAGE } from './constants' import { ApiErrorCode, type BackendApiBooleanResponse } from './types' import { FrontendApiError } from '../libs/FrontendApiError' import { newUserError } from '../libs/UserError' -import type { ControlUnit } from '../domain/entities/controlUnit' - export const ARCHIVE_CONTROL_UNITE_RESOURCE_ERROR_MESSAGE = "Nous n'avons pas pu archiver ce moyen." const CAN_DELETE_CONTROL_UNIT_RESOURCE_ERROR_MESSAGE = "Nous n'avons pas pu vérifier si ce moyen est supprimable." export const DELETE_CONTROL_UNIT_RESOURCE_ERROR_MESSAGE = diff --git a/frontend/src/api/controlUnitsAPI.ts b/frontend/src/api/controlUnitsAPI.ts index 81259d941..b4e19cd72 100644 --- a/frontend/src/api/controlUnitsAPI.ts +++ b/frontend/src/api/controlUnitsAPI.ts @@ -1,10 +1,10 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' + import { monitorenvPublicApi } from './api' import { ApiErrorCode, type BackendApiBooleanResponse } from './types' import { FrontendApiError } from '../libs/FrontendApiError' import { newUserError } from '../libs/UserError' -import type { ControlUnit } from '../domain/entities/controlUnit' - const CAN_DELETE_CONTROL_UNIT_ERROR_MESSAGE = "Nous n'avons pas pu vérifier si cette unité de contrôle est supprimable." export const DELETE_CONTROL_UNIT_ERROR_MESSAGE = [ 'Cette unité est rattachée à des missions ou des signalements.', diff --git a/frontend/src/api/missionsAPI.ts b/frontend/src/api/missionsAPI.ts index 8d4eae7c7..c28458fdd 100644 --- a/frontend/src/api/missionsAPI.ts +++ b/frontend/src/api/missionsAPI.ts @@ -1,6 +1,7 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' + import { monitorenvPrivateApi, monitorenvPublicApi } from './api' import { ApiErrorCode } from './types' -import { ControlUnit } from '../domain/entities/controlUnit' import { MissionSourceEnum, type Mission, type MissionData } from '../domain/entities/missions' import { FrontendApiError } from '../libs/FrontendApiError' diff --git a/frontend/src/domain/entities/administration.ts b/frontend/src/domain/entities/administration.ts index 2369b181f..d8aed7dda 100644 --- a/frontend/src/domain/entities/administration.ts +++ b/frontend/src/domain/entities/administration.ts @@ -1,4 +1,4 @@ -import type { ControlUnit } from './controlUnit' +import { type ControlUnit } from '@mtes-mct/monitor-ui' export namespace Administration { export interface Administration { diff --git a/frontend/src/domain/entities/controlUnit.ts b/frontend/src/domain/entities/controlUnit.ts deleted file mode 100644 index 2d0c0b5b6..000000000 --- a/frontend/src/domain/entities/controlUnit.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { MissionSourceEnum } from './missions' - -import type { Administration } from './administration' -import type { DepartmentArea } from './departmentArea' -import type { Station } from './station' - -export namespace ControlUnit { - export interface ControlUnit { - administration: Administration.AdministrationData - administrationId: number - /** Area of intervention for this unit. */ - areaNote: string | undefined - controlUnitContactIds: number[] - controlUnitContacts: ControlUnitContactData[] - controlUnitResourceIds: number[] - // `ControlUnitResource` and not `ControlUnitResourceData` because we need `base` data for each resource - controlUnitResources: ControlUnitResource[] - departmentArea: DepartmentArea.DepartmentArea | undefined - /** `departmentAreaInseeCode` is the `departmentArea` ID. */ - departmentAreaInseeCode: string | undefined - id: number - isArchived: boolean - name: string - /** Conditions under which this unit should be contacted. */ - termsNote: string | undefined - } - - export interface ControlUnitContact { - controlUnit: ControlUnitData - controlUnitId: number - email: string | undefined - id: number - name: string - phone: string | undefined - } - - export interface ControlUnitResource { - controlUnit: ControlUnitData - controlUnitId: number - id: number - isArchived: boolean - name: string - note: string | undefined - /** Base64 Data URI. */ - photo: string | undefined - station: Station.StationData - stationId: number - type: ControlUnitResourceType - } - - // --------------------------------------------------------------------------- - // Constants - - export enum ControlUnitContactPredefinedName { - ADJUNCT = 'Adjoint', - BRIDGE = 'Passerelle', - COMMANDER = 'Commandant', - COMMANDER_A = 'Commandant bordée A', - COMMANDER_B = 'Commandant bordée B', - CREW_A = 'Équipage A', - CREW_B = 'Équipage B', - DML = 'DML', - DOCK = 'Quai', - LAND = 'Terre', - LAND_ON_CALL = 'Permanence terre', - NEAR_COAST = 'Proche côte', - OFFICE = 'Bureau de l’unité', - ONBOARD_PHONE = 'Téléphone de bord', - ON_CALL = 'Permanence', - OPERATIONAL_CENTER = 'Centre opérationnel', - OPERATIONAL_CENTER_HNO = 'Centre opérationnel - HNO', // Heures Non-Ouvrées - OPERATIONAL_CENTER_HO = 'Centre opérationnel - HO', // Heures Ouvrées - PERMANENT_CONTACT_ONBOARD = 'Contact permanent à bord', - SEA = 'Mer', - SECRETARIAT = 'Secrétariat', - SERVICE_CHIEF = 'Chef de service', - UNIT_CHIEF = 'Chef d’unité', - UNKNOWN = 'Nom de contact à renseigner' - } - - // Don't forget to mirror any update here in both Postgre & Backend enums. - export enum ControlUnitResourceType { - AIRPLANE = 'AIRPLANE', - BARGE = 'BARGE', - CAR = 'CAR', - DRONE = 'DRONE', - EQUESTRIAN = 'EQUESTRIAN', - FAST_BOAT = 'FAST_BOAT', - FRIGATE = 'FRIGATE', - HELICOPTER = 'HELICOPTER', - HYDROGRAPHIC_SHIP = 'HYDROGRAPHIC_SHIP', - KAYAK = 'KAYAK', - LIGHT_FAST_BOAT = 'LIGHT_FAST_BOAT', - MINE_DIVER = 'MINE_DIVER', - MOTORCYCLE = 'MOTORCYCLE', - NET_LIFTER = 'NET_LIFTER', - NO_RESOURCE = 'NO_RESOURCE', - OTHER = 'OTHER', - PATROL_BOAT = 'PATROL_BOAT', - PEDESTRIAN = 'PEDESTRIAN', - PIROGUE = 'PIROGUE', - RIGID_HULL = 'RIGID_HULL', - SEA_SCOOTER = 'SEA_SCOOTER', - SEMI_RIGID = 'SEMI_RIGID', - SUPPORT_SHIP = 'SUPPORT_SHIP', - TRAINING_SHIP = 'TRAINING_SHIP', - TUGBOAT = 'TUGBOAT' - } - export enum ControlUnitResourceTypeLabel { - AIRPLANE = 'Avion', - BARGE = 'Barge', - CAR = 'Voiture', - DRONE = 'Drône', - EQUESTRIAN = 'Équestre', - FAST_BOAT = 'Vedette', - FRIGATE = 'Frégate', - HELICOPTER = 'Hélicoptère', - HYDROGRAPHIC_SHIP = 'Bâtiment hydrographique', - KAYAK = 'Kayak', - LIGHT_FAST_BOAT = 'Vedette légère', - MINE_DIVER = 'Plongeur démineur', - MOTORCYCLE = 'Moto', - NET_LIFTER = 'Remonte-filets', - NO_RESOURCE = 'Aucun moyen', - OTHER = 'Autre', - PATROL_BOAT = 'Patrouilleur', - PEDESTRIAN = 'Piéton', - PIROGUE = 'Pirogue', - RIGID_HULL = 'Coque rigide', - SEA_SCOOTER = 'Scooter de mer', - SEMI_RIGID = 'Semi-rigide', - SUPPORT_SHIP = 'Bâtiment de soutien', - TRAINING_SHIP = 'Bâtiment-école', - TUGBOAT = 'Remorqueur' - } - - // List of PAM units identifiers - // 10141 PAM Gyptis - // 10404 PAM Iris - // 10121 PAM Jeanne Barret - // 10345 PAM Osiris - // 10080 PAM Themis - export const PAMControlUnitIds = [10141, 10404, 10121, 10345, 10080] - - // --------------------------------------------------------------------------- - // Types - - export type ControlUnitData = Omit< - ControlUnit, - | 'administration' - | 'controlUnitContactIds' - | 'controlUnitContacts' - | 'controlUnitResourceIds' - | 'controlUnitResources' - | 'departmentArea' - > - export type NewControlUnitData = Omit - - export type ControlUnitContactData = Omit - export type NewControlUnitContactData = Omit - - export type ControlUnitResourceData = Omit - export type NewControlUnitResourceData = Omit - - export type EngagedControlUnit = { - controlUnit: ControlUnit - missionSources: MissionSourceEnum[] - } -} diff --git a/frontend/src/domain/entities/legacyControlUnit.ts b/frontend/src/domain/entities/legacyControlUnit.ts index 31faa190d..6520bb683 100644 --- a/frontend/src/domain/entities/legacyControlUnit.ts +++ b/frontend/src/domain/entities/legacyControlUnit.ts @@ -1,4 +1,4 @@ -import type { ControlUnit } from './controlUnit' +import { type ControlUnit } from '@mtes-mct/monitor-ui' export type LegacyControlUnit = { administration: string diff --git a/frontend/src/domain/entities/station.ts b/frontend/src/domain/entities/station.ts index 91f3841d2..433f0e0f4 100644 --- a/frontend/src/domain/entities/station.ts +++ b/frontend/src/domain/entities/station.ts @@ -1,4 +1,4 @@ -import type { ControlUnit } from './controlUnit' +import { type ControlUnit } from '@mtes-mct/monitor-ui' export namespace Station { export interface Station { diff --git a/frontend/src/domain/use_cases/missions/addMission.ts b/frontend/src/domain/use_cases/missions/addMission.ts index 9d4baa7a8..0aa8f72d4 100644 --- a/frontend/src/domain/use_cases/missions/addMission.ts +++ b/frontend/src/domain/use_cases/missions/addMission.ts @@ -1,3 +1,4 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' import { chain } from 'lodash' import { generatePath } from 'react-router' @@ -10,7 +11,6 @@ import { sideWindowPaths } from '../../entities/sideWindow' import type { HomeAppThunk } from '../../../store' import type { ReportingDetailed } from '../../entities/reporting' -import type { ControlUnit } from '@mtes-mct/monitor-ui' export const addMission = ({ diff --git a/frontend/src/features/Administration/components/AdministrationForm/constants.tsx b/frontend/src/features/Administration/components/AdministrationForm/constants.tsx index a09ee0c7b..6921d8665 100644 --- a/frontend/src/features/Administration/components/AdministrationForm/constants.tsx +++ b/frontend/src/features/Administration/components/AdministrationForm/constants.tsx @@ -1,11 +1,10 @@ -import { Icon, Size } from '@mtes-mct/monitor-ui' +import { type ControlUnit, Icon, Size } from '@mtes-mct/monitor-ui' import { object, string } from 'yup' import { NavIconButton } from '../../../../ui/NavIconButton' import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOffice/components/BackofficeMenu/constants' import type { AdministrationFormValues } from './types' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { ColumnDef } from '@tanstack/react-table' export const ADMINISTRATION_FORM_SCHEMA = object({ diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/AreaNote.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/AreaNote.tsx index 41bdab247..6c3781a23 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/AreaNote.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/AreaNote.tsx @@ -1,10 +1,9 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' import styled from 'styled-components' import { Section } from './shared/Section' import { TextareaForm } from './shared/TextareaForm' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' - type AreaNoteProps = { controlUnit: ControlUnit.ControlUnit onSubmit: (nextControlUnit: ControlUnit.ControlUnit) => any diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FieldWithButton.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FieldWithButton.tsx new file mode 100644 index 000000000..1bb43d9e0 --- /dev/null +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FieldWithButton.tsx @@ -0,0 +1,43 @@ +import { IconButton } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +// TODO Replace this dirty hack with an `IconButton` prop in some monitor-ui fields. +const BareFieldWithIconButton = styled.div<{ + $hasError: boolean +}>` + align-items: flex-end; + display: flex; + + > *:first-child { + flex-grow: 1; + margin-right: 4px; + } + + > .Element-IconButton { + margin-bottom: ${p => (p.$hasError ? '22px' : 0)}; + } +` + +const IconButtonOff = styled(IconButton)` + background-color: ${p => p.theme.color.white}; + border-color: ${p => p.theme.color.white}; + color: ${p => p.theme.color.charcoal}; + padding: 4px; + + &:hover { + background-color: ${p => p.theme.color.white}; + border-color: ${p => p.theme.color.white}; + color: ${p => p.theme.color.blueYonder}; + } +` + +const IconButtonOn = styled(IconButtonOff)` + background-color: ${p => p.theme.color.charcoal}; + border-color: ${p => p.theme.color.charcoal}; + color: ${p => p.theme.color.white}; +` + +export const FieldWithButton = Object.assign(BareFieldWithIconButton, { + IconButtonOff, + IconButtonOn +}) diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikEmailField.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikEmailField.tsx new file mode 100644 index 000000000..a85375122 --- /dev/null +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikEmailField.tsx @@ -0,0 +1,110 @@ +import { Accent, Button, ControlUnit, FormikTextInput, Icon, Level, Message } from '@mtes-mct/monitor-ui' +import { useFormikContext } from 'formik' +import { useState } from 'react' +import styled from 'styled-components' + +import { FieldWithButton } from './FieldWithButton' + +import type { ControlUnitContactFormValues } from '../types' + +type FormikIsEmailSubscriptionContactToggleProps = { + controlUnit: ControlUnit.ControlUnit +} +export function FormikEmailField({ controlUnit }: FormikIsEmailSubscriptionContactToggleProps) { + const { errors, setFieldValue, values } = useFormikContext() + + const [isConfirmationMessageOpened, setIsConfirmationMessageOpened] = useState(false) + const [otherContactSubscribedEmail, setOtherContactSubscribedEmail] = useState(undefined) + + const askForConfirmation = () => { + // If the user is trying to subscribe this contact while another contact is already subscribed, ask for confirmation + if (!values.isEmailSubscriptionContact) { + const otherEmailSubscriptionContact = controlUnit.controlUnitContacts.find( + controlUnitContact => controlUnitContact.id !== values.id && controlUnitContact.isEmailSubscriptionContact + ) + if (otherEmailSubscriptionContact && otherEmailSubscriptionContact.email) { + setOtherContactSubscribedEmail(otherEmailSubscriptionContact.email) + setIsConfirmationMessageOpened(true) + + return + } + } + + toggleSubscription() + } + + const closeConfirmationMessage = () => { + setIsConfirmationMessageOpened(false) + setOtherContactSubscribedEmail(undefined) + } + + const toggleSubscription = () => { + setIsConfirmationMessageOpened(false) + + const willBeSubscribed = !values.isEmailSubscriptionContact + + setFieldValue('isEmailSubscriptionContact', willBeSubscribed) + } + + return ( + <> + + + {values.isEmailSubscriptionContact ? ( + + ) : ( + + )} + + + {values.isEmailSubscriptionContact && ( + +

+ Adresse de diffusion +
+ Cette adresse est utilisée pour envoyer à l’unité les préavis de débarquement ainsi que le bilan + hebdomadaire de ses activités de contrôle. +

+
+ )} + + {isConfirmationMessageOpened && otherContactSubscribedEmail && ( + +

+ Attention +
+ Attention l’adresse actuelle de diffusion à cette unité est : {otherContactSubscribedEmail}.{' '} + Voulez-vous la remplacer par : {values.email} ? +

+ + + + + +
+ )} + + ) +} + +const ActionBar = styled.div` + display: flex; + flex-direction: column; + margin-top: 16px; + + > .Element-Button:not(:first-child) { + margin-top: 8px; + } +` diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikNameSelect.tsx similarity index 92% rename from frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx rename to frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikNameSelect.tsx index 1463e9b8e..f69c6c35a 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikNameSelect.tsx @@ -1,4 +1,4 @@ -import { Accent, FormikTextInput, Icon, IconButton, Select } from '@mtes-mct/monitor-ui' +import { Accent, ControlUnit, FormikTextInput, Icon, IconButton, Select } from '@mtes-mct/monitor-ui' import { useField } from 'formik' import { useCallback, useEffect, useState } from 'react' import styled from 'styled-components' @@ -6,8 +6,7 @@ import styled from 'styled-components' import { CONTROL_UNIT_CONTACT_PREDEFINED_NAMES, SORTED_CONTROL_UNIT_CONTACT_PREDEFINED_NAMES_AS_OPTIONS -} from './constants' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' +} from '../constants' export function FormikNameSelect() { const [field, meta, helpers] = useField('name') diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikPhoneField.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikPhoneField.tsx new file mode 100644 index 000000000..0dbe8b3e6 --- /dev/null +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/FormikPhoneField.tsx @@ -0,0 +1,34 @@ +import { FormikTextInput, Icon } from '@mtes-mct/monitor-ui' +import { useFormikContext } from 'formik' + +import { FieldWithButton } from './FieldWithButton' + +import type { ControlUnitContactFormValues } from '../types' + +export function FormikPhoneField() { + const { errors, setFieldValue, values } = useFormikContext() + + const toggleSubscription = () => { + setFieldValue('isSmsSubscriptionContact', !values.isSmsSubscriptionContact) + } + + return ( + + + + {values.isSmsSubscriptionContact ? ( + + ) : ( + + )} + + ) +} diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/index.tsx similarity index 73% rename from frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx rename to frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/index.tsx index a8d9f7d12..d221aeb9a 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form/index.tsx @@ -1,23 +1,26 @@ -import { Accent, Button, FormikTextInput, Icon, IconButton, THEME, useKey } from '@mtes-mct/monitor-ui' +import { Accent, Button, ControlUnit, Icon, IconButton, THEME, useKey } from '@mtes-mct/monitor-ui' import { Formik } from 'formik' import styled from 'styled-components' -import { CONTROL_UNIT_CONTACT_FORM_SCHEMA } from './constants' +import { FormikEmailField } from './FormikEmailField' import { FormikNameSelect } from './FormikNameSelect' +import { FormikPhoneField } from './FormikPhoneField' +import { CONTROL_UNIT_CONTACT_FORM_SCHEMA } from '../constants' -import type { ControlUnitContactFormValues } from './types' +import type { ControlUnitContactFormValues } from '../types' import type { CSSProperties } from 'react' import type { Promisable } from 'type-fest' export type FormProps = { className?: string + controlUnit: ControlUnit.ControlUnit initialValues: ControlUnitContactFormValues onCancel: () => Promisable onDelete?: () => Promisable onSubmit: (controlUnitContactFormValues: ControlUnitContactFormValues) => void style?: CSSProperties } -export function Form({ className, initialValues, onCancel, onDelete, onSubmit, style }: FormProps) { +export function Form({ className, controlUnit, initialValues, onCancel, onDelete, onSubmit, style }: FormProps) { const key = useKey([initialValues]) const isNew = !initialValues.id @@ -33,10 +36,10 @@ export function Form({ className, initialValues, onCancel, onDelete, onSubmit, s {({ handleSubmit }) => (
{isNew ? 'Ajouter un contact' : 'Éditer un contact'} - + - - + +
@@ -75,7 +78,8 @@ const StyledForm = styled.form` background-color: ${p => p.theme.color.gainsboro}; padding: 16px; - > div:not(:first-child) { + > div:not(:first-child, .Component-Banner), + .Component-Message { margin-top: 16px; } ` @@ -91,7 +95,8 @@ const ActionBar = styled.div` } ` -// TODO Add `borderColor` in Monitor UI. +// TODO Add prop `Level` in Monitor UI to ``. Why is there a "DANGER" both in `Accent` and `Level`? +// TODO Check padding in monitor-ui. const DeleteButton = styled(IconButton)` border-color: ${p => p.theme.color.maximumRed}; padding: 0 4px; diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Item.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Item.tsx index 11606d289..d6f042304 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Item.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Item.tsx @@ -1,9 +1,7 @@ -import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' +import { Accent, ControlUnit, Icon, IconButton, Link } from '@mtes-mct/monitor-ui' import { useCallback } from 'react' import styled from 'styled-components' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' - import type { Promisable } from 'type-fest' export type ItemProps = { @@ -23,11 +21,17 @@ export function Item({ controlUnitContact, onEdit }: ItemProps) { {ControlUnit.ControlUnitContactPredefinedName[controlUnitContact.name] || controlUnitContact.name} {controlUnitContact.phone} + {controlUnitContact.isSmsSubscriptionContact && ( + + )}

- + {controlUnitContact.email} - + + {controlUnitContact.isEmailSubscriptionContact && ( + + )}

@@ -51,6 +55,17 @@ const Wrapper = styled.div` const Left = styled.div` flex-grow: 1; + + > p { + align-items: center; + display: flex; + line-height: 18px; + + > .Element-IconBox { + margin-left: 8px; + margin-bottom: -1px; + } + } ` const Right = styled.div`` diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/constants.ts b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/constants.ts index 091fa026b..8d2a57af9 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/constants.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/constants.ts @@ -1,7 +1,6 @@ -import { getOptionsFromLabelledEnum } from '@mtes-mct/monitor-ui' +import { ControlUnit, getOptionsFromLabelledEnum } from '@mtes-mct/monitor-ui' import { object, string } from 'yup' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' import { sortCollectionByLocalizedProps } from '../../../../../utils/sortCollectionByLocalizedProps' import type { ControlUnitContactFormValues } from './types' @@ -24,6 +23,8 @@ export const CONTROL_UNIT_CONTACT_FORM_SCHEMA = object().shape( export const INITIAL_CONTROL_UNIT_CONTACT_FORM_VALUES: ControlUnitContactFormValues = { controlUnitId: undefined, email: undefined, + isEmailSubscriptionContact: false, + isSmsSubscriptionContact: false, name: undefined, phone: undefined } diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/index.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/index.tsx index eee457e2b..94439db32 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/index.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/index.tsx @@ -1,33 +1,31 @@ -import { Accent, Button, Icon } from '@mtes-mct/monitor-ui' -import { useCallback, useMemo, useState } from 'react' +import { mainWindowActions } from '@features/MainWindow/slice' +import { addMainWindowBanner } from '@features/MainWindow/useCases/addMainWindowBanner' +import { Accent, Button, type ControlUnit, Icon, Level } from '@mtes-mct/monitor-ui' +import { useCallback, useMemo, useRef, useState } from 'react' import styled from 'styled-components' import { INITIAL_CONTROL_UNIT_CONTACT_FORM_VALUES } from './constants' import { Form } from './Form' import { Item } from './Item' import { sortControlUnitContactsByQualifiedName } from './utils' -import { - controlUnitContactsAPI, - useCreateControlUnitContactMutation, - useUpdateControlUnitContactMutation -} from '../../../../../api/controlUnitContactsAPI' +import { controlUnitContactsAPI } from '../../../../../api/controlUnitContactsAPI' import { ConfirmationModal } from '../../../../../components/ConfirmationModal' import { useAppDispatch } from '../../../../../hooks/useAppDispatch' import { FrontendError } from '../../../../../libs/FrontendError' +import { createOrUpdateControlUnitContact } from '../../../useCases/createOrUpdateControlUnitContact' import { Section } from '../shared/Section' import { TextareaForm } from '../shared/TextareaForm' import type { ControlUnitContactFormValues } from './types' -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' type ControlUnitContactListProps = { controlUnit: ControlUnit.ControlUnit onSubmit: (nextControlUnit: ControlUnit.ControlUnit) => any } export function ControlUnitContactList({ controlUnit, onSubmit }: ControlUnitContactListProps) { + const noEmailSubscriptionContactWarningBannerIdRef = useRef(undefined) + const dispatch = useAppDispatch() - const [createControlUnitContact] = useCreateControlUnitContactMutation() - const [updateControlUnitContact] = useUpdateControlUnitContactMutation() const [editedControlUnitContactId, setEditedControlUnitContactId] = useState(undefined) const [isDeletionConfirmationModalOpen, setIsDeletionConfirmationModalOpen] = useState(false) @@ -60,29 +58,54 @@ export function ControlUnitContactList({ controlUnit, onSubmit }: ControlUnitCon setIsNewControlUnitContactFormOpen(false) }, []) - const confirmDeletion = useCallback(async () => { + const confirmDeletion = async () => { if (!editedControlUnitContactId) { throw new FrontendError('`editedControlUnitContactId` is undefined.') } + // There can only be one email subscription contact per control unit, + // meaning that if the user is trying to delete the only email subscription contact, + // we need to warn them that the control unit will no longer receive any email + const controlUnitContactToDelete = controlUnit.controlUnitContacts.find( + controlUnitContact => controlUnitContact.id === editedControlUnitContactId + ) + if (!controlUnitContactToDelete) { + throw new FrontendError('`deletedControlUnitContact` is undefined.') + } + if (controlUnitContactToDelete.isEmailSubscriptionContact) { + noEmailSubscriptionContactWarningBannerIdRef.current = showNoEmailSubscriptionContactWarningBanner() + } + await dispatch(controlUnitContactsAPI.endpoints.deleteControlUnitContact.initiate(editedControlUnitContactId)) closeDialogsAndModals() closeForm() - }, [closeDialogsAndModals, closeForm, dispatch, editedControlUnitContactId]) - - const createOrUpdateControlUnitContact = useCallback( - async (controlUnitContactFormValues: ControlUnitContactFormValues) => { - if (isNewControlUnitContactFormOpen) { - await createControlUnitContact(controlUnitContactFormValues as ControlUnit.NewControlUnitContactData) - } else { - await updateControlUnitContact(controlUnitContactFormValues as ControlUnit.ControlUnitContactData) - } - - closeForm() - }, - [closeForm, createControlUnitContact, isNewControlUnitContactFormOpen, updateControlUnitContact] - ) + } + + const submit = async (controlUnitContactFormValues: ControlUnitContactFormValues) => { + const hadAnEmailSubscriptionContact = controlUnit.controlUnitContacts.some( + controlUnitContact => controlUnitContact.isEmailSubscriptionContact + ) + const willHaveAnEmailSubscriptionContact = + controlUnitContactFormValues.isEmailSubscriptionContact || + controlUnit.controlUnitContacts.some( + controlUnitContact => + controlUnitContact.id !== controlUnitContactFormValues.id && controlUnitContact.isEmailSubscriptionContact + ) + + // There can only be one email subscription contact per control unit, + // meaning that if the user is trying to unsubscribe the current email subscription contact, + // we need to warn them that the control unit will no longer receive any email + if (hadAnEmailSubscriptionContact && !willHaveAnEmailSubscriptionContact) { + noEmailSubscriptionContactWarningBannerIdRef.current = showNoEmailSubscriptionContactWarningBanner() + } else { + hideNoEmailSubscriptionContactWarningBannerIfAny() + } + + dispatch(createOrUpdateControlUnitContact(controlUnitContactFormValues)) + + closeForm() + } const openCreationForm = useCallback(() => { setEditedControlUnitContactId(undefined) @@ -94,6 +117,32 @@ export function ControlUnitContactList({ controlUnit, onSubmit }: ControlUnitCon setIsNewControlUnitContactFormOpen(false) }, []) + const hideNoEmailSubscriptionContactWarningBannerIfAny = () => { + if (noEmailSubscriptionContactWarningBannerIdRef.current === undefined) { + return + } + + dispatch(mainWindowActions.removeBanner(noEmailSubscriptionContactWarningBannerIdRef.current)) + + noEmailSubscriptionContactWarningBannerIdRef.current = undefined + } + + /** + * @returns ID of the banner that was added to the main window. + */ + const showNoEmailSubscriptionContactWarningBanner = () => + dispatch( + addMainWindowBanner({ + children: + 'Cette unité n’a actuellement plus d’adresse de diffusion. Elle ne recevra plus de préavis ni de bilan de ses activités de contrôle.', + closingDelay: 115000, + isClosable: true, + isFixed: true, + level: Level.WARNING, + withAutomaticClosing: true + }) + ) + return (
Contacts @@ -108,10 +157,12 @@ export function ControlUnitContactList({ controlUnit, onSubmit }: ControlUnitCon {sortedControlUnitContacts.map(controlUnitContact => controlUnitContact.id === editedControlUnitContactId ? ( ) : ( @@ -120,9 +171,10 @@ export function ControlUnitContactList({ controlUnit, onSubmit }: ControlUnitCon {isNewControlUnitContactFormOpen ? ( ) : (
diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/types.ts b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/types.ts index 849a0ddf3..b02eaca26 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/types.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/types.ts @@ -1,8 +1,8 @@ -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' -import type { UndefineExceptArrays } from '@mtes-mct/monitor-ui' +import type { ControlUnit, UndefineExcept } from '@mtes-mct/monitor-ui' -export type ControlUnitContactFormValues = UndefineExceptArrays< - ControlUnit.ControlUnitContactData | ControlUnit.NewControlUnitContactData +export type ControlUnitContactFormValues = UndefineExcept< + ControlUnit.ControlUnitContactData | ControlUnit.NewControlUnitContactData, + 'isEmailSubscriptionContact' | 'isSmsSubscriptionContact' > & { id?: number } diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/utils.ts b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/utils.ts index a01899ffd..762cd4024 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/utils.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/utils.ts @@ -1,9 +1,8 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' import { sortBy } from 'lodash/fp' import { CONTROL_UNIT_CONTACT_PREDEFINED_NAMES } from './constants' -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' - /** * Sort control unit contacts by qualified name. * diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Item.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Item.tsx index 9817335f6..7dce7c5bf 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Item.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Item.tsx @@ -1,9 +1,8 @@ -import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' +import { Accent, ControlUnit, Icon, IconButton } from '@mtes-mct/monitor-ui' import { useCallback } from 'react' import styled from 'styled-components' import { Placeholder } from './Placeholder' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' import type { Promisable } from 'type-fest' diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Placeholder.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Placeholder.tsx index 81a7ac0aa..8a6f679cc 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Placeholder.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Placeholder.tsx @@ -1,7 +1,7 @@ +import { ControlUnit } from '@mtes-mct/monitor-ui' import styled from 'styled-components' import { getIconFromControlUnitResourceType } from './utils' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' type PlaceholderProps = { type: ControlUnit.ControlUnitResourceType diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/constants.ts b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/constants.ts index 0b0f0724d..aa0a633ec 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/constants.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/constants.ts @@ -1,8 +1,6 @@ -import { getOptionsFromLabelledEnum } from '@mtes-mct/monitor-ui' +import { ControlUnit, getOptionsFromLabelledEnum } from '@mtes-mct/monitor-ui' import { object, string } from 'yup' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' - import type { ControlUnitResourceFormValues } from './types' export const CONTROL_UNIT_RESOURCE_FORM_SCHEMA = object().shape({ diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/index.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/index.tsx index bd7844f3f..ec18cb542 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/index.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/index.tsx @@ -1,6 +1,6 @@ // import styled from 'styled-components' -import { Accent, Button, Icon, THEME } from '@mtes-mct/monitor-ui' +import { Accent, Button, ControlUnit, Icon, THEME } from '@mtes-mct/monitor-ui' import { useCallback, useState } from 'react' import styled from 'styled-components' @@ -15,7 +15,6 @@ import { } from '../../../../../api/controlUnitResourcesAPI' import { ConfirmationModal } from '../../../../../components/ConfirmationModal' import { Dialog } from '../../../../../components/Dialog' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' import { useAppDispatch } from '../../../../../hooks/useAppDispatch' import { FrontendError } from '../../../../../libs/FrontendError' import { isEmptyish } from '../../../../../utils/isEmptyish' diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/types.ts b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/types.ts index 360f860e8..fcc1ad4c4 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/types.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/types.ts @@ -1,5 +1,4 @@ -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' -import type { UndefineExceptArrays } from '@mtes-mct/monitor-ui' +import { type ControlUnit, type UndefineExceptArrays } from '@mtes-mct/monitor-ui' export type ControlUnitResourceFormValues = UndefineExceptArrays & { id?: number diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/utils.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/utils.tsx index 53c8cbec8..8257e5e88 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/utils.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/utils.tsx @@ -1,8 +1,6 @@ import { ControlUnit, Icon, getControlUnitResourceCategoryFromType } from '@mtes-mct/monitor-ui' -import { ControlUnit as LocalControlUnit } from '../../../../../domain/entities/controlUnit' - -export function getIconFromControlUnitResourceType(type: LocalControlUnit.ControlUnitResourceType) { +export function getIconFromControlUnitResourceType(type: ControlUnit.ControlUnitResourceType) { const category = getControlUnitResourceCategoryFromType(type) switch (category) { diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx index fb75434c6..315312586 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx @@ -1,8 +1,7 @@ -import { Accent, Button, Label, Textarea, type TextareaProps } from '@mtes-mct/monitor-ui' +import { Accent, Button, type ControlUnit, Label, Textarea, type TextareaProps } from '@mtes-mct/monitor-ui' import { useCallback, useState, type FormEvent, type ChangeEvent } from 'react' import styled from 'styled-components' -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' import type { Promisable } from 'type-fest' type TextareaFormProps = Omit & { diff --git a/frontend/src/features/ControlUnit/components/ControlUnitForm/constants.tsx b/frontend/src/features/ControlUnit/components/ControlUnitForm/constants.tsx index a2cfab908..30b37ba77 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitForm/constants.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitForm/constants.tsx @@ -1,7 +1,7 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' import { number, object, string } from 'yup' import type { ControlUnitFormValues } from './types' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { ColumnDef } from '@tanstack/react-table' export const CONTROL_UNIT_CONTACT_TABLE_COLUMNS: Array> = [ diff --git a/frontend/src/features/ControlUnit/components/ControlUnitForm/index.tsx b/frontend/src/features/ControlUnit/components/ControlUnitForm/index.tsx index 37e896baf..3dcbe3eaa 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitForm/index.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitForm/index.tsx @@ -1,6 +1,7 @@ import { Accent, Button, + type ControlUnit, DataTable, FormikCheckbox, FormikSelect, @@ -31,7 +32,6 @@ import { isNotArchived } from '../../../../utils/isNotArchived' import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOffice/components/BackofficeMenu/constants' import type { ControlUnitFormValues } from './types' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' export function ControlUnitForm() { const { controlUnitId } = useParams() diff --git a/frontend/src/features/ControlUnit/components/ControlUnitForm/types.ts b/frontend/src/features/ControlUnit/components/ControlUnitForm/types.ts index f541c6bc4..b9e4ac720 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitForm/types.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitForm/types.ts @@ -1,4 +1,3 @@ -import type { ControlUnit } from '../../../../domain/entities/controlUnit' -import type { UndefineExceptArrays } from '@mtes-mct/monitor-ui' +import type { ControlUnit, UndefineExceptArrays } from '@mtes-mct/monitor-ui' export type ControlUnitFormValues = UndefineExceptArrays diff --git a/frontend/src/features/ControlUnit/components/ControlUnitListDialog/Item.tsx b/frontend/src/features/ControlUnit/components/ControlUnitListDialog/Item.tsx index c0ae6f264..b151db529 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitListDialog/Item.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitListDialog/Item.tsx @@ -1,4 +1,4 @@ -import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' +import { Accent, type ControlUnit, Icon, IconButton } from '@mtes-mct/monitor-ui' import { property, uniqBy } from 'lodash/fp' import { createEmpty, extend } from 'ol/extent' import { fromLonLat } from 'ol/proj' @@ -15,8 +15,6 @@ import { mainWindowActions } from '../../../MainWindow/slice' import { stationActions } from '../../../Station/slice' import { controlUnitDialogActions } from '../ControlUnitDialog/slice' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' - export type ItemProps = { controlUnit: ControlUnit.ControlUnit } diff --git a/frontend/src/features/ControlUnit/components/ControlUnitListDialog/types.ts b/frontend/src/features/ControlUnit/components/ControlUnitListDialog/types.ts index 2d83591d2..0a4f12c2f 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitListDialog/types.ts +++ b/frontend/src/features/ControlUnit/components/ControlUnitListDialog/types.ts @@ -1,4 +1,4 @@ -import type { ControlUnit } from '@mtes-mct/monitor-ui' +import { type ControlUnit } from '@mtes-mct/monitor-ui' export type FiltersState = { administrationId?: number diff --git a/frontend/src/features/ControlUnit/components/ControlUnitTable/constants.tsx b/frontend/src/features/ControlUnit/components/ControlUnitTable/constants.tsx index d7a87c63c..421036343 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitTable/constants.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitTable/constants.tsx @@ -1,9 +1,8 @@ -import { Icon, Size } from '@mtes-mct/monitor-ui' +import { type ControlUnit, Icon, Size } from '@mtes-mct/monitor-ui' import { NavIconButton } from '../../../../ui/NavIconButton' import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOffice/components/BackofficeMenu/constants' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { ColumnDef } from '@tanstack/react-table' export const CONTROL_UNIT_TABLE_COLUMNS: Array> = [ diff --git a/frontend/src/features/ControlUnit/components/ControlUnitTable/index.tsx b/frontend/src/features/ControlUnit/components/ControlUnitTable/index.tsx index f64c5b290..78423152b 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitTable/index.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitTable/index.tsx @@ -1,4 +1,4 @@ -import { DataTable, THEME } from '@mtes-mct/monitor-ui' +import { type ControlUnit, DataTable, THEME } from '@mtes-mct/monitor-ui' import { useCallback, useMemo, useState } from 'react' import styled from 'styled-components' @@ -19,7 +19,6 @@ import { useAppSelector } from '../../../../hooks/useAppSelector' import { NavButton } from '../../../../ui/NavButton' import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOffice/components/BackofficeMenu/constants' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { CellContext } from '@tanstack/react-table' export function ControlUnitTable() { diff --git a/frontend/src/features/ControlUnit/components/ControlUnitTable/utils.tsx b/frontend/src/features/ControlUnit/components/ControlUnitTable/utils.tsx index 3437f8083..3c04f5e6c 100644 --- a/frontend/src/features/ControlUnit/components/ControlUnitTable/utils.tsx +++ b/frontend/src/features/ControlUnit/components/ControlUnitTable/utils.tsx @@ -1,9 +1,8 @@ -import { CustomSearch, type Filter, Icon, IconButton, Size } from '@mtes-mct/monitor-ui' +import { type ControlUnit, CustomSearch, type Filter, Icon, IconButton, Size } from '@mtes-mct/monitor-ui' import { CONTROL_UNIT_TABLE_COLUMNS } from './constants' import type { FiltersState } from './types' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { CellContext, ColumnDef } from '@tanstack/react-table' import type { Promisable } from 'type-fest' diff --git a/frontend/src/features/ControlUnit/useCases/createOrUpdateControlUnitContact.ts b/frontend/src/features/ControlUnit/useCases/createOrUpdateControlUnitContact.ts new file mode 100644 index 000000000..b3a96207f --- /dev/null +++ b/frontend/src/features/ControlUnit/useCases/createOrUpdateControlUnitContact.ts @@ -0,0 +1,21 @@ +import { controlUnitContactsAPI } from '@api/controlUnitContactsAPI' + +import type { ControlUnitContactFormValues } from '../components/ControlUnitDialog/ControlUnitContactList/types' +import type { ControlUnit } from '@mtes-mct/monitor-ui' +import type { HomeAppThunk } from '@store/index' + +export const createOrUpdateControlUnitContact = + (controlUnitContactFormValues: ControlUnitContactFormValues): HomeAppThunk> => + async dispatch => { + if (controlUnitContactFormValues.id === undefined) { + const newControlUnitContact = controlUnitContactFormValues as ControlUnit.NewControlUnitContactData + + await dispatch(controlUnitContactsAPI.endpoints.createControlUnitContact.initiate(newControlUnitContact)).unwrap() + + return + } + + const nextControlUnitContact = controlUnitContactFormValues as ControlUnit.ControlUnitContactData + + await dispatch(controlUnitContactsAPI.endpoints.patchControlUnitContact.initiate(nextControlUnitContact)).unwrap() + } diff --git a/frontend/src/features/MainWindow/components/BannerStack/Item.tsx b/frontend/src/features/MainWindow/components/BannerStack/Item.tsx new file mode 100644 index 000000000..d06e633d5 --- /dev/null +++ b/frontend/src/features/MainWindow/components/BannerStack/Item.tsx @@ -0,0 +1,28 @@ +import { Banner, type BannerProps } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +import type { BannerStackItemProps } from '../../types' +import type { Promisable } from 'type-fest' + +type ItemProps = { + bannerProps: BannerStackItemProps + bannerStackRank: number + onCloseOrAutoclose: (bannerStackKey: number) => Promisable +} +export function Item({ bannerProps, bannerStackRank, onCloseOrAutoclose }: ItemProps) { + const controlledBannerProps: BannerProps = { + ...bannerProps, + onAutoClose: () => onCloseOrAutoclose(bannerStackRank), + onClose: () => onCloseOrAutoclose(bannerStackRank), + top: '0' + } + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ) +} + +const StyledBanner = styled(Banner)` + position: static; +` diff --git a/frontend/src/features/MainWindow/components/BannerStack/index.tsx b/frontend/src/features/MainWindow/components/BannerStack/index.tsx new file mode 100644 index 000000000..3c367cb5c --- /dev/null +++ b/frontend/src/features/MainWindow/components/BannerStack/index.tsx @@ -0,0 +1,41 @@ +import { useAppDispatch } from '@hooks/useAppDispatch' +import { useAppSelector } from '@hooks/useAppSelector' +import { isDefined } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +import { Item } from './Item' +import { mainWindowActions } from '../../slice' + +// TODO Implement top positionning with a visible ``. +// Use as relative `` wrapper for that, not a calculation. +export function BannerStack() { + const dispatch = useAppDispatch() + const bannerStack = useAppSelector(state => state.mainWindow.bannerStack) + + const remove = (bannerStackRank: number) => { + dispatch(mainWindowActions.removeBanner(bannerStackRank)) + } + + const bannerStackItems = Object.values(bannerStack.entities).filter(isDefined) + + if (bannerStackItems.length === 0) { + return <> + } + + return ( + + {bannerStackItems.map(({ id, props }) => ( + + ))} + + ) +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + left: 0; + position: absolute; + top: 0; + width: 100%; +` diff --git a/frontend/src/features/MainWindow/slice.ts b/frontend/src/features/MainWindow/slice.ts index 542c9f6e2..20663fa22 100644 --- a/frontend/src/features/MainWindow/slice.ts +++ b/frontend/src/features/MainWindow/slice.ts @@ -1,10 +1,19 @@ -import { createSlice, type PayloadAction } from '@reduxjs/toolkit' +import { createEntityAdapter, createSlice, type EntityState, type PayloadAction } from '@reduxjs/toolkit' + +import type { BannerStackItem } from './types' + +export const bannerStackAdapter = createEntityAdapter({ + selectId: (bannerStackItem: BannerStackItem) => bannerStackItem.id, + sortComparer: (a, b) => a.id - b.id +}) interface MainWindowState { + bannerStack: EntityState hasFullHeightRightDialogOpen: boolean isRightMenuOpened: boolean } const INITIAL_STATE: MainWindowState = { + bannerStack: bannerStackAdapter.getInitialState(), hasFullHeightRightDialogOpen: false, isRightMenuOpened: false } @@ -13,6 +22,24 @@ const mainWindowSlice = createSlice({ initialState: INITIAL_STATE, name: 'mainWindow', reducers: { + /** + * Add a banner to the stack. + * + * @internal /!\ This action is not meant to be dispatched directly. Use `addMainWindowBanner()` dispatcher instead. + */ + addBanner(state, action: PayloadAction) { + bannerStackAdapter.addOne(state.bannerStack, action.payload) + }, + + /** + * Remove a banner from the stack. + * + * @param action.payload ID of the banner to remove. + */ + removeBanner(state, action: PayloadAction) { + bannerStackAdapter.removeOne(state.bannerStack, action.payload) + }, + setHasFullHeightRightDialogOpen(state, action: PayloadAction) { state.hasFullHeightRightDialogOpen = action.payload }, diff --git a/frontend/src/features/MainWindow/types.ts b/frontend/src/features/MainWindow/types.ts index cae3cbac7..ba7409dfe 100644 --- a/frontend/src/features/MainWindow/types.ts +++ b/frontend/src/features/MainWindow/types.ts @@ -1,3 +1,13 @@ +import type { BannerProps } from '@mtes-mct/monitor-ui' + +export type BannerStackItem = { + id: number + props: BannerStackItemProps +} +export type BannerStackItemProps = Omit & { + children: string +} + export enum MainWindowConfirmationModalActionType { 'DELETE_CONTROL_UNIT_CONTACT' = 'DELETE_CONTROL_UNIT_CONTACT', 'DELETE_CONTROL_UNIT_RESOURCE' = 'DELETE_CONTROL_UNIT_RESOURCE' diff --git a/frontend/src/features/MainWindow/useCases/addMainWindowBanner.ts b/frontend/src/features/MainWindow/useCases/addMainWindowBanner.ts new file mode 100644 index 000000000..6a773eed2 --- /dev/null +++ b/frontend/src/features/MainWindow/useCases/addMainWindowBanner.ts @@ -0,0 +1,22 @@ +import { bannerStackAdapter, mainWindowActions } from '../slice' + +import type { BannerStackItem, BannerStackItemProps } from '../types' +import type { HomeAppThunk } from '@store/index' + +/** + * Add a banner to the main window. + * + * @param props Component props of the `` to add. + * @returns ID of the added banner (used to remove it if needed). + */ +export const addMainWindowBanner = + (props: BannerStackItemProps): HomeAppThunk => + (dispatch, getState) => { + const { bannerStack } = getState().mainWindow + const nextId = bannerStackAdapter.getSelectors().selectTotal(bannerStack) + 1 + const bannerStackItem: BannerStackItem = { id: nextId, props } + + dispatch(mainWindowActions.addBanner(bannerStackItem)) + + return nextId + } diff --git a/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/PointPicker.tsx b/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/PointPicker.tsx index 8029123f4..6346ca4b1 100644 --- a/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/PointPicker.tsx +++ b/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/PointPicker.tsx @@ -91,13 +91,14 @@ const Field = styled.div` ` const Center = styled.div` cursor: pointer; + display: flex; margin-left: auto; margin-right: 8px; color: ${p => p.theme.color.slateGray}; text-decoration: underline; - > div { - vertical-align: middle; - padding-right: 8px; + + > .Element-IconBox { + margin-right: 8px; } ` diff --git a/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/ZonePicker.tsx b/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/ZonePicker.tsx index 3032f4c14..85a4c8865 100644 --- a/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/ZonePicker.tsx +++ b/frontend/src/features/Reportings/ReportingForm/FormComponents/Position/ZonePicker.tsx @@ -86,13 +86,14 @@ const Field = styled.div` ` const Center = styled.a` cursor: pointer; + display: flex; margin-left: auto; margin-right: 8px; color: ${p => p.theme.color.slateGray}; text-decoration: underline; - > div { - vertical-align: middle; - padding-right: 8px; + + > .Element-IconBox { + margin-right: 8px; } ` diff --git a/frontend/src/features/Station/components/StationOverlay/StationCard/Item.tsx b/frontend/src/features/Station/components/StationOverlay/StationCard/Item.tsx index 33d7a1e4c..1c8272188 100644 --- a/frontend/src/features/Station/components/StationOverlay/StationCard/Item.tsx +++ b/frontend/src/features/Station/components/StationOverlay/StationCard/Item.tsx @@ -1,3 +1,4 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' import styled from 'styled-components' import { displayControlUnitResourcesFromControlUnit } from './utils' @@ -5,8 +6,6 @@ import { globalActions } from '../../../../../domain/shared_slices/Global' import { useAppDispatch } from '../../../../../hooks/useAppDispatch' import { controlUnitDialogActions } from '../../../../ControlUnit/components/ControlUnitDialog/slice' -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' - type ItemProps = { controlUnit: ControlUnit.ControlUnit stationId: number diff --git a/frontend/src/features/Station/components/StationOverlay/StationCard/index.tsx b/frontend/src/features/Station/components/StationOverlay/StationCard/index.tsx index 181455ee0..f2c0c4b2a 100644 --- a/frontend/src/features/Station/components/StationOverlay/StationCard/index.tsx +++ b/frontend/src/features/Station/components/StationOverlay/StationCard/index.tsx @@ -1,4 +1,4 @@ -import { MapMenuDialog } from '@mtes-mct/monitor-ui' +import { type ControlUnit, MapMenuDialog } from '@mtes-mct/monitor-ui' import { uniq } from 'lodash/fp' import { useCallback, useEffect, useState } from 'react' import styled from 'styled-components' @@ -12,7 +12,6 @@ import { useHasMapInteraction } from '../../../../../hooks/useHasMapInteraction' import { FrontendError } from '../../../../../libs/FrontendError' import { stationActions } from '../../../slice' -import type { ControlUnit } from '../../../../../domain/entities/controlUnit' import type { Station } from '../../../../../domain/entities/station' import type { FeatureLike } from 'ol/Feature' diff --git a/frontend/src/features/Station/components/StationOverlay/StationCard/utils.tsx b/frontend/src/features/Station/components/StationOverlay/StationCard/utils.tsx index c63d6cf7e..2dec87f90 100644 --- a/frontend/src/features/Station/components/StationOverlay/StationCard/utils.tsx +++ b/frontend/src/features/Station/components/StationOverlay/StationCard/utils.tsx @@ -1,7 +1,6 @@ -import { Tag, pluralize } from '@mtes-mct/monitor-ui' +import { ControlUnit, Tag, pluralize } from '@mtes-mct/monitor-ui' import { isEmpty } from 'lodash/fp' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' import { getIconFromControlUnitResourceType } from '../../../../ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/utils' export function displayControlUnitResourcesFromControlUnit(controlUnit: ControlUnit.ControlUnit, stationId: number) { diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/MultiPointPicker.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/MultiPointPicker.tsx index 97ec6fd70..4215459b7 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/MultiPointPicker.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/MultiPointPicker.tsx @@ -137,13 +137,14 @@ const Field = styled.div` ` const Center = styled.div` cursor: pointer; + display: flex; margin-left: auto; margin-right: 8px; color: ${p => p.theme.color.slateGray}; text-decoration: underline; - > div { - vertical-align: middle; - padding-right: 8px; + + > .Element-IconBox { + margin-right: 8px; } ` diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/OtherControlTypesForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/OtherControlTypesForm.tsx index 2ad837f58..31df5c179 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/OtherControlTypesForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/OtherControlTypesForm.tsx @@ -1,9 +1,7 @@ -import { FormikCheckbox } from '@mtes-mct/monitor-ui' +import { ControlUnit, FormikCheckbox } from '@mtes-mct/monitor-ui' import { useFormikContext } from 'formik' import styled from 'styled-components' -import { ControlUnit } from '../../../../../domain/entities/controlUnit' - import type { Mission } from '../../../../../domain/entities/missions' export function OtherControlTypesForm({ currentActionIndex }: { currentActionIndex: number }) { diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/Location.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/Location.tsx index 14a6ebe8a..8e0431494 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/Location.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/Location.tsx @@ -96,13 +96,14 @@ const ZoneWrapper = styled.div` const Center = styled.div` cursor: pointer; + display: flex; margin-left: auto; margin-right: 8px; color: ${p => p.theme.color.slateGray}; text-decoration: underline; - > div { - vertical-align: middle; - padding-right: 8px; + + > .Element-IconBox { + margin-right: 8px; } ` diff --git a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/SurveillanceZonePicker.tsx b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/SurveillanceZonePicker.tsx index 34b01f9d8..b5d3f9b59 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/SurveillanceZonePicker.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/SurveillanceZonePicker.tsx @@ -123,13 +123,14 @@ const Field = styled.div` ` const Center = styled.a` cursor: pointer; + display: flex; margin-left: auto; margin-right: 8px; color: ${p => p.theme.color.slateGray}; text-decoration: underline; - > div { - vertical-align: middle; - padding-right: 8px; + + > .Element-IconBox { + margin-right: 8px; } ` diff --git a/frontend/src/features/missions/MissionForm/ControlUnitsForm/ControlUnitSelector.tsx b/frontend/src/features/missions/MissionForm/ControlUnitsForm/ControlUnitSelector.tsx index 36e0913d6..d6afb4cf9 100644 --- a/frontend/src/features/missions/MissionForm/ControlUnitsForm/ControlUnitSelector.tsx +++ b/frontend/src/features/missions/MissionForm/ControlUnitsForm/ControlUnitSelector.tsx @@ -1,5 +1,6 @@ import { Accent, + type ControlUnit, CustomSearch, FormikTextInput, getOptionsFromIdAndName, @@ -24,8 +25,6 @@ import { isNotArchived } from '../../../../utils/isNotArchived' import { getMissionPageRoute } from '../../../../utils/routes' import { missionFormsActions } from '../slice' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' - type ControlUnitSelectorProps = { controlUnitIndex: number removeControlUnit: () => void diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index bc5ed75a9..46e94ce44 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -1,4 +1,13 @@ -import { Banner, customDayjs, FormikEffect, Icon, Level, THEME, usePrevious } from '@mtes-mct/monitor-ui' +import { + Banner, + type ControlUnit, + customDayjs, + FormikEffect, + Icon, + Level, + THEME, + usePrevious +} from '@mtes-mct/monitor-ui' import { useMissionEventContext } from 'context/useMissionEventContext' import { useFormikContext } from 'formik' import { isEmpty } from 'lodash' @@ -38,7 +47,6 @@ import { useAppSelector } from '../../../hooks/useAppSelector' import { sideWindowActions } from '../../SideWindow/slice' import { getIsMissionEnded } from '../utils' -import type { ControlUnit } from '../../../domain/entities/controlUnit' import type { AtLeast } from '../../../types' enum ModalTypes { diff --git a/frontend/src/features/missions/MissionForm/MissionZonePicker.tsx b/frontend/src/features/missions/MissionForm/MissionZonePicker.tsx index d46436604..0f38397c7 100644 --- a/frontend/src/features/missions/MissionForm/MissionZonePicker.tsx +++ b/frontend/src/features/missions/MissionForm/MissionZonePicker.tsx @@ -141,13 +141,14 @@ const Field = styled.div` ` const Center = styled.a` cursor: pointer; + display: flex; margin-left: auto; margin-right: 8px; color: ${p => p.theme.color.slateGray}; text-decoration: underline; - > div { - vertical-align: middle; - padding-right: 8px; + + > .Element-IconBox { + margin-right: 8px; } ` diff --git a/frontend/src/features/missions/MissionForm/Schemas/index.ts b/frontend/src/features/missions/MissionForm/Schemas/index.ts index 2661d73ac..cc4c4430e 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/index.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/index.ts @@ -1,4 +1,5 @@ import { getIsMissionEnded } from '@features/missions/utils' +import { type ControlUnit } from '@mtes-mct/monitor-ui' import * as Yup from 'yup' import { getCompletionEnvActionControlSchema, getNewEnvActionControlSchema } from './Control' @@ -11,7 +12,6 @@ import { } from '../../../../domain/entities/missions' import { HIDDEN_ERROR } from '../constants' -import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { LegacyControlUnit } from '../../../../domain/entities/legacyControlUnit' const MissionTypesSchema = Yup.array() diff --git a/frontend/src/features/missions/MissionForm/hooks/useUpdateOtherControlTypes.ts b/frontend/src/features/missions/MissionForm/hooks/useUpdateOtherControlTypes.ts index b4fa04f0a..686f058fc 100644 --- a/frontend/src/features/missions/MissionForm/hooks/useUpdateOtherControlTypes.ts +++ b/frontend/src/features/missions/MissionForm/hooks/useUpdateOtherControlTypes.ts @@ -1,7 +1,6 @@ -import { usePrevious } from '@mtes-mct/monitor-ui' +import { ControlUnit, usePrevious } from '@mtes-mct/monitor-ui' import { useFormikContext } from 'formik' -import { ControlUnit } from '../../../../domain/entities/controlUnit' import { ActionTypeEnum, type Mission } from '../../../../domain/entities/missions' export const useUpdateOtherControlTypes = () => { diff --git a/frontend/src/features/missions/MissionForm/slice.ts b/frontend/src/features/missions/MissionForm/slice.ts index 0a98e0792..b3eca2c54 100644 --- a/frontend/src/features/missions/MissionForm/slice.ts +++ b/frontend/src/features/missions/MissionForm/slice.ts @@ -1,6 +1,6 @@ +import { type ControlUnit } from '@mtes-mct/monitor-ui' import { createSlice, type PayloadAction } from '@reduxjs/toolkit' -import type { ControlUnit } from '../../../domain/entities/controlUnit' import type { Mission, NewMission } from '../../../domain/entities/missions' import type { AtLeast } from '../../../types' diff --git a/frontend/src/features/missions/MissionsNavBar.tsx b/frontend/src/features/missions/MissionsNavBar.tsx index 37818876d..8b2f97f48 100644 --- a/frontend/src/features/missions/MissionsNavBar.tsx +++ b/frontend/src/features/missions/MissionsNavBar.tsx @@ -95,6 +95,7 @@ const StyledResponsiveNav = styled(ResponsiveNav)` display: flex; box-shadow: 0px 3px 4px #7077854d; height: 48px; + > .rs-nav-item { width: 360px; border-radius: 0px !important; @@ -110,11 +111,13 @@ const StyledResponsiveNav = styled(ResponsiveNav)` font-weight: 500; border-radius: 0px; border: 0px !important; + > .rs-icon { color: ${p => p.theme.color.slateGray} !important; } } - > span { + + > span:not(.Element-IconBox) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 1bf683464..79bb8e065 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,3 +1,4 @@ +import { BannerStack } from '@features/MainWindow/components/BannerStack' import { useCallback, useMemo } from 'react' import { useBeforeUnload } from 'react-router-dom' import { ToastContainer } from 'react-toastify' @@ -67,8 +68,11 @@ export function HomePage() { return ( <> - + {/* TODO Move this wrapper to `@features/MainWindow/components/MainWindowLayout.tsx`. */} + + + @@ -88,10 +92,10 @@ export function HomePage() { - - + + ) }